mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Wear UI wip
This commit is contained in:
+17
-19
@@ -111,11 +111,11 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = freeCompilerArgs + listOf(
|
||||
"-Xopt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-Xopt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-Xopt-in=kotlin.time.ExperimentalTime",
|
||||
"-Xopt-in=kotlin.RequiresOptIn"
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlin.time.ExperimentalTime",
|
||||
"-opt-in=kotlin.RequiresOptIn"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -140,30 +140,28 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app-common"))
|
||||
|
||||
implementation("androidx.wear:wear:1.2.0")
|
||||
implementation("androidx.wear.tiles:tiles-material:1.1.0")
|
||||
implementation("com.google.android.horologist:horologist-tiles:0.1.5")
|
||||
implementation("com.google.android.gms:play-services-wearable:17.1.0")
|
||||
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
|
||||
implementation(project(":app-common"))
|
||||
|
||||
addBaseKotlin()
|
||||
|
||||
addDagger()
|
||||
|
||||
// Debugging
|
||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
||||
implementation("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||
|
||||
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
||||
|
||||
addBaseWorkManager()
|
||||
|
||||
addBaseAndroid()
|
||||
addBaseAndroidUi()
|
||||
implementation("com.google.android.material:material:1.5.0-rc01")
|
||||
|
||||
addNavigation()
|
||||
|
||||
addTesting()
|
||||
|
||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
||||
implementation("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||
|
||||
implementation("androidx.wear:wear:1.2.0")
|
||||
implementation("androidx.wear.tiles:tiles-material:1.1.0")
|
||||
implementation("com.google.android.horologist:horologist-tiles:0.1.5")
|
||||
implementation("com.google.android.gms:play-services-wearable:17.1.0")
|
||||
}
|
||||
@@ -3,6 +3,26 @@
|
||||
package="eu.darken.capod">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_COARSE_LOCATION"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
@@ -11,6 +31,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:name=".App"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<uses-library
|
||||
android:name="com.google.android.wearable"
|
||||
@@ -31,7 +52,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<service
|
||||
android:name=".wear.core.CAPodTileService"
|
||||
android:description="@string/wear_service_description"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.darken.capod
|
||||
|
||||
import android.app.Application
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import com.getkeepsafe.relinker.ReLinker
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import eu.darken.capod.common.coroutine.AppScope
|
||||
import eu.darken.capod.common.debug.autoreport.AutoReporting
|
||||
import eu.darken.capod.common.debug.logging.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
open class App : Application(), Configuration.Provider {
|
||||
|
||||
@Inject lateinit var workerFactory: HiltWorkerFactory
|
||||
@Inject lateinit var autoReporting: AutoReporting
|
||||
@Inject @AppScope lateinit var appScope: CoroutineScope
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
|
||||
|
||||
ReLinker
|
||||
.log { message -> log(TAG) { "ReLinker: $message" } }
|
||||
.loadLibrary(this, "bugsnag-plugin-android-anr")
|
||||
|
||||
autoReporting.setup()
|
||||
|
||||
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration = Configuration.Builder()
|
||||
.setMinimumLoggingLevel(android.util.Log.VERBOSE)
|
||||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
|
||||
companion object {
|
||||
internal val TAG = logTag("CAP")
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,27 @@
|
||||
package eu.darken.capod.wear.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import eu.darken.capod.databinding.ActivityMainBinding
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.navigation.findNavController
|
||||
import eu.darken.capod.common.uix.Activity2
|
||||
import eu.darken.capod.databinding.MainActivityBinding
|
||||
|
||||
class MainActivity : Activity() {
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : Activity2() {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private val vm: MainActivityVM by viewModels()
|
||||
private lateinit var ui: MainActivityBinding
|
||||
private val navController by lazy { supportFragmentManager.findNavController(R.id.nav_host) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
ui = MainActivityBinding.inflate(layoutInflater)
|
||||
setContentView(ui.root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ package eu.darken.capod.wear.ui
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.uix.ViewModel2
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.monitor.core.PodMonitor
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -11,4 +16,31 @@ import javax.inject.Inject
|
||||
class MainActivityVM @Inject constructor(
|
||||
handle: SavedStateHandle,
|
||||
dispatcherProvider: DispatcherProvider,
|
||||
) : ViewModel2(dispatcherProvider = dispatcherProvider)
|
||||
private val podMonitor: PodMonitor,
|
||||
private val permissionTool: PermissionTool,
|
||||
) : ViewModel2(dispatcherProvider = dispatcherProvider) {
|
||||
|
||||
sealed class State {
|
||||
data class PermissionRequired(
|
||||
val permissions: List<Permission>,
|
||||
) : State()
|
||||
|
||||
data class Devices(
|
||||
val devices: List<PodDevice>
|
||||
) : State()
|
||||
}
|
||||
|
||||
val state = combine(
|
||||
podMonitor.devices,
|
||||
permissionTool.missingPermissions,
|
||||
) { devices, missingPermissions ->
|
||||
when {
|
||||
missingPermissions.isNotEmpty() -> State.PermissionRequired(
|
||||
permissions = missingPermissions.toList()
|
||||
)
|
||||
else -> State.Devices(
|
||||
devices = devices
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package eu.darken.capod.wear.ui.overview
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import eu.darken.capod.common.lists.BindableVH
|
||||
import eu.darken.capod.common.lists.differ.AsyncDiffer
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.common.lists.differ.HasAsyncDiffer
|
||||
import eu.darken.capod.common.lists.differ.setupDiffer
|
||||
import eu.darken.capod.common.lists.modular.ModularAdapter
|
||||
import eu.darken.capod.common.lists.modular.mods.DataBinderMod
|
||||
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
|
||||
import eu.darken.capod.wear.ui.overview.cards.BluetoothDisabledVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.MissingMainDeviceVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.PermissionCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.DualPodsCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.SinglePodsCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||
import javax.inject.Inject
|
||||
|
||||
class OverviewAdapter @Inject constructor() :
|
||||
ModularAdapter<OverviewAdapter.BaseVH<OverviewAdapter.Item, ViewBinding>>(),
|
||||
HasAsyncDiffer<OverviewAdapter.Item> {
|
||||
|
||||
override val asyncDiffer: AsyncDiffer<*, Item> = setupDiffer()
|
||||
|
||||
init {
|
||||
modules.add(DataBinderMod(data))
|
||||
modules.add(TypedVHCreatorMod({ data[it] is PermissionCardVH.Item }) { PermissionCardVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is DualPodsCardVH.Item }) { DualPodsCardVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is SinglePodsCardVH.Item }) { SinglePodsCardVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is MissingMainDeviceVH.Item }) { MissingMainDeviceVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is BluetoothDisabledVH.Item }) { BluetoothDisabledVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
abstract class BaseVH<D : Item, B : ViewBinding>(
|
||||
@LayoutRes layoutId: Int,
|
||||
parent: ViewGroup
|
||||
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B>
|
||||
|
||||
interface Item : DifferItem
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package eu.darken.capod.wear.ui.overview
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.BuildConfig
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.colorString
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.lists.differ.update
|
||||
import eu.darken.capod.common.lists.setupDefaults
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.uix.Fragment3
|
||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
import eu.darken.capod.common.viewbinding.viewBinding
|
||||
import eu.darken.capod.databinding.MainFragmentBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
|
||||
override val vm: OverviewFragmentVM by viewModels()
|
||||
override val ui: MainFragmentBinding by viewBinding()
|
||||
|
||||
@Inject
|
||||
lateinit var adapter: OverviewAdapter
|
||||
|
||||
lateinit var permissionLauncher: ActivityResultLauncher<String>
|
||||
var awaitingPermission = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
awaitingPermission = savedInstanceState?.getBoolean("awaitingPermission") ?: false
|
||||
|
||||
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||
log { "Request for $id was granted=$granted" }
|
||||
vm.onPermissionResult(granted)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
ui.apply {
|
||||
list.setupDefaults(adapter, dividers = false)
|
||||
}
|
||||
|
||||
ui.toolbar.apply {
|
||||
// setOnMenuItemClickListener {
|
||||
// when (it.itemId) {
|
||||
// R.id.menu_item_settings -> {
|
||||
// vm.goToSettings()
|
||||
// true
|
||||
// }
|
||||
// R.id.menu_item_donate -> {
|
||||
// vm.onUpgrade()
|
||||
// true
|
||||
// }
|
||||
// R.id.menu_item_upgrade -> {
|
||||
// vm.onUpgrade()
|
||||
// true
|
||||
// }
|
||||
// else -> false
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
vm.listItems.observe2(ui) {
|
||||
if (BuildConfig.DEBUG) toolbar.subtitle = "${it.size} items"
|
||||
adapter.update(it)
|
||||
}
|
||||
|
||||
vm.workerAutolaunch.observe2 {
|
||||
// While UI is active, subscribe to the autolaunch routine
|
||||
}
|
||||
|
||||
vm.requestPermissionEvent.observe2(ui) {
|
||||
when (it) {
|
||||
Permission.IGNORE_BATTERY_OPTIMIZATION -> {
|
||||
awaitingPermission = true
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
Uri.parse("package:${requireContext().packageName}")
|
||||
)
|
||||
)
|
||||
}
|
||||
Permission.SYSTEM_ALERT_WINDOW -> {
|
||||
awaitingPermission = true
|
||||
startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:${requireContext().packageName}")
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
permissionLauncher.launch(it.permissionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.upgradeState.observe2(ui) { info ->
|
||||
// val gplay = toolbar.menu.findItem(R.id.menu_item_upgrade)
|
||||
// val donate = toolbar.menu.findItem(R.id.menu_item_donate)
|
||||
// gplay.isVisible = false
|
||||
// donate.isVisible = false
|
||||
//
|
||||
// val baseTitle = when (info.type) {
|
||||
// UpgradeRepo.Type.GPLAY -> {
|
||||
// if (info.isPro) {
|
||||
// getString(R.string.app_name_pro)
|
||||
// } else {
|
||||
// gplay.isVisible = true
|
||||
// getString(R.string.app_name)
|
||||
// }
|
||||
// }
|
||||
// UpgradeRepo.Type.FOSS -> {
|
||||
// if (info.isPro) {
|
||||
// getString(R.string.app_name_foss)
|
||||
// } else {
|
||||
// donate.isVisible = true
|
||||
// getString(R.string.app_name)
|
||||
// }
|
||||
// }
|
||||
// }.split(" ".toRegex())
|
||||
// .dropLastWhile { it.isEmpty() }
|
||||
// .toTypedArray()
|
||||
//
|
||||
// toolbar.title = if (baseTitle.size == 2) {
|
||||
// val builder = SpannableStringBuilder(baseTitle[0] + " ")
|
||||
// val color = when (info.type) {
|
||||
// UpgradeRepo.Type.FOSS -> R.color.brand_secondary
|
||||
// else -> R.color.brand_tertiary
|
||||
// }
|
||||
// builder.append(colorString(requireContext(), color, baseTitle[1]))
|
||||
// } else {
|
||||
// getString(R.string.app_name)
|
||||
// }
|
||||
}
|
||||
vm.launchUpgradeFlow.observe2 {
|
||||
it(requireActivity())
|
||||
}
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean("awaitingPermission", awaitingPermission)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (awaitingPermission) {
|
||||
awaitingPermission = false
|
||||
log { "awaitingPermission=true" }
|
||||
vm.onPermissionResult(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package eu.darken.capod.wear.ui.overview
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.flow.combine
|
||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||
import eu.darken.capod.common.navigation.navVia
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.uix.ViewModel3
|
||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.monitor.core.PodMonitor
|
||||
import eu.darken.capod.pods.core.DualPodDevice
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.SinglePodDevice
|
||||
import eu.darken.capod.wear.ui.overview.cards.BluetoothDisabledVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.MissingMainDeviceVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.PermissionCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.DualPodsCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.SinglePodsCardVH
|
||||
import eu.darken.capod.wear.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class OverviewFragmentVM @Inject constructor(
|
||||
@Suppress("UNUSED_PARAMETER") handle: SavedStateHandle,
|
||||
dispatcherProvider: DispatcherProvider,
|
||||
// private val monitorControl: MonitorControl,
|
||||
private val podMonitor: PodMonitor,
|
||||
private val permissionTool: PermissionTool,
|
||||
private val generalSettings: GeneralSettings,
|
||||
debugSettings: DebugSettings,
|
||||
private val upgradeRepo: UpgradeRepo,
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
||||
|
||||
val upgradeState = upgradeRepo.upgradeInfo.asLiveData2()
|
||||
val launchUpgradeFlow = SingleLiveEvent<(Activity) -> Unit>()
|
||||
|
||||
private val updateTicker = channelFlow<Unit> {
|
||||
while (isActive) {
|
||||
trySend(Unit)
|
||||
delay(3000)
|
||||
}
|
||||
}
|
||||
|
||||
val workerAutolaunch: LiveData<Unit> = permissionTool.missingPermissions
|
||||
.onEach {
|
||||
if (it.isNotEmpty()) {
|
||||
log(TAG) { "Missing permissions: $it" }
|
||||
return@onEach
|
||||
}
|
||||
|
||||
val shouldStartMonitor = when (generalSettings.monitorMode.value) {
|
||||
MonitorMode.MANUAL -> false
|
||||
MonitorMode.AUTOMATIC -> bluetoothManager.connectedDevices().first().isNotEmpty()
|
||||
MonitorMode.ALWAYS -> true
|
||||
}
|
||||
if (shouldStartMonitor) {
|
||||
log(TAG) { "Starting monitor" }
|
||||
// monitorControl.startMonitor()
|
||||
}
|
||||
}
|
||||
.map { }
|
||||
.asLiveData2()
|
||||
|
||||
val requestPermissionEvent = SingleLiveEvent<Permission>()
|
||||
|
||||
private val pods: Flow<List<PodDevice>> = permissionTool.missingPermissions
|
||||
.flatMapLatest { permissions ->
|
||||
if (permissions.isNotEmpty()) {
|
||||
return@flatMapLatest flowOf(emptyList())
|
||||
}
|
||||
|
||||
generalSettings.showAll.flow.flatMapLatest { showAll ->
|
||||
if (showAll) {
|
||||
podMonitor.devices
|
||||
} else {
|
||||
podMonitor.mainDevice.map { mainDevice ->
|
||||
mainDevice?.let { listOf(it) } ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.catch { errorEvents.postValue(it) }
|
||||
|
||||
val listItems: LiveData<List<OverviewAdapter.Item>> = combine(
|
||||
updateTicker,
|
||||
permissionTool.missingPermissions,
|
||||
pods,
|
||||
debugSettings.isDebugModeEnabled.flow,
|
||||
generalSettings.showAll.flow,
|
||||
bluetoothManager.isBluetoothEnabled,
|
||||
podMonitor.mainDevice,
|
||||
) { _, permissions, pods, isDebugMode, showAll, isBluetoothEnabled, mainPod ->
|
||||
val items = mutableListOf<OverviewAdapter.Item>()
|
||||
|
||||
permissions
|
||||
.map { perm ->
|
||||
PermissionCardVH.Item(
|
||||
permission = perm,
|
||||
onRequest = { requestPermissionEvent.postValue(it) },
|
||||
)
|
||||
}
|
||||
.run { items.addAll(this) }
|
||||
|
||||
if (permissions.isEmpty()) {
|
||||
if (!isBluetoothEnabled) {
|
||||
items.add(0, BluetoothDisabledVH.Item)
|
||||
} else if (mainPod == null) {
|
||||
items.add(0, MissingMainDeviceVH.Item)
|
||||
}
|
||||
}
|
||||
|
||||
if (permissions.isEmpty() && isBluetoothEnabled) {
|
||||
pods.map {
|
||||
val now = Instant.now()
|
||||
when (it) {
|
||||
is DualPodDevice -> DualPodsCardVH.Item(
|
||||
now = now,
|
||||
device = it,
|
||||
showDebug = isDebugMode,
|
||||
isMainPod = it == mainPod,
|
||||
)
|
||||
is SinglePodDevice -> SinglePodsCardVH.Item(
|
||||
now = now,
|
||||
device = it,
|
||||
showDebug = isDebugMode,
|
||||
isMainPod = it == mainPod,
|
||||
)
|
||||
else -> UnknownPodDeviceCardVH.Item(
|
||||
now = now,
|
||||
device = it,
|
||||
showDebug = isDebugMode,
|
||||
isMainPod = it == mainPod,
|
||||
)
|
||||
}
|
||||
}.run { items.addAll(this) }
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
.catch { errorEvents.postValue(it) }
|
||||
.asLiveData2()
|
||||
|
||||
fun onPermissionResult(granted: Boolean) {
|
||||
if (granted) permissionTool.recheck()
|
||||
}
|
||||
|
||||
fun goToSettings() = launch {
|
||||
OverviewFragmentDirections.actionOverviewFragmentToSettingsFragment().navVia(this@OverviewFragmentVM)
|
||||
}
|
||||
|
||||
fun onUpgrade() = launch {
|
||||
val call: (Activity) -> Unit = {
|
||||
upgradeRepo.launchBillingFlow(it)
|
||||
}
|
||||
launchUpgradeFlow.postValue(call)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards
|
||||
|
||||
import android.view.ViewGroup
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.databinding.OverviewBluetoothDisabledItemBinding
|
||||
import eu.darken.capod.wear.ui.overview.OverviewAdapter
|
||||
|
||||
class BluetoothDisabledVH(parent: ViewGroup) :
|
||||
OverviewAdapter.BaseVH<BluetoothDisabledVH.Item, OverviewBluetoothDisabledItemBinding>(
|
||||
R.layout.overview_bluetooth_disabled_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy {
|
||||
OverviewBluetoothDisabledItemBinding.bind(itemView)
|
||||
}
|
||||
|
||||
override val onBindData: OverviewBluetoothDisabledItemBinding.(
|
||||
item: Item,
|
||||
payloads: List<Any>
|
||||
) -> Unit = binding(payload = true) { item ->
|
||||
|
||||
}
|
||||
|
||||
object Item : OverviewAdapter.Item {
|
||||
override val stableId: Long = Item::class.hashCode().toLong()
|
||||
|
||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
|
||||
get() = { old, new -> if (new::class.isInstance(old)) new else null }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards
|
||||
|
||||
import android.view.ViewGroup
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.databinding.OverviewNomaindeviceItemBinding
|
||||
import eu.darken.capod.wear.ui.overview.OverviewAdapter
|
||||
|
||||
class MissingMainDeviceVH(parent: ViewGroup) :
|
||||
OverviewAdapter.BaseVH<MissingMainDeviceVH.Item, OverviewNomaindeviceItemBinding>(
|
||||
R.layout.overview_nomaindevice_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy {
|
||||
OverviewNomaindeviceItemBinding.bind(itemView)
|
||||
}
|
||||
|
||||
override val onBindData: OverviewNomaindeviceItemBinding.(
|
||||
item: Item,
|
||||
payloads: List<Any>
|
||||
) -> Unit = binding(payload = true) { item ->
|
||||
|
||||
}
|
||||
|
||||
object Item : OverviewAdapter.Item {
|
||||
override val stableId: Long = Item::class.hashCode().toLong()
|
||||
|
||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
|
||||
get() = { old, new -> if (new::class.isInstance(old)) new else null }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards
|
||||
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.ViewGroup
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.PrivacyPolicy
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.databinding.OverviewPermissionItemBinding
|
||||
import eu.darken.capod.wear.ui.overview.OverviewAdapter
|
||||
|
||||
class PermissionCardVH(parent: ViewGroup) :
|
||||
OverviewAdapter.BaseVH<PermissionCardVH.Item, OverviewPermissionItemBinding>(
|
||||
R.layout.overview_permission_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy {
|
||||
OverviewPermissionItemBinding.bind(itemView)
|
||||
}
|
||||
|
||||
override val onBindData: OverviewPermissionItemBinding.(
|
||||
item: Item,
|
||||
payloads: List<Any>
|
||||
) -> Unit = binding(payload = true) { item ->
|
||||
permissionLabel.setText(item.permission.labelRes)
|
||||
permissionDescription.setText(item.permission.descriptionRes)
|
||||
grantAction.setOnClickListener { item.onRequest(item.permission) }
|
||||
privacyPolicy.apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
val ppText = getString(R.string.settings_privacy_policy_label)
|
||||
val ppLink = PrivacyPolicy.URL
|
||||
text = Html.fromHtml("<html><a href=\"$ppLink\">$ppText</a></html>", 0)
|
||||
}
|
||||
}
|
||||
|
||||
data class Item(
|
||||
val permission: Permission,
|
||||
val onRequest: (Permission) -> Unit
|
||||
) : OverviewAdapter.Item {
|
||||
override val stableId: Long = permission.hashCode().toLong()
|
||||
|
||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
|
||||
get() = { old, new -> if (new::class.isInstance(old)) new else null }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards.pods
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.databinding.OverviewPodsDualItemBinding
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||
import eu.darken.capod.pods.core.apple.DualAirPods.LidState
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class DualPodsCardVH(parent: ViewGroup) :
|
||||
PodDeviceVH<DualPodsCardVH.Item, OverviewPodsDualItemBinding>(
|
||||
R.layout.overview_pods_dual_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy { OverviewPodsDualItemBinding.bind(itemView) }
|
||||
|
||||
override val onBindData = binding(payload = true) { item: Item ->
|
||||
val device = item.device
|
||||
name.apply {
|
||||
val sb = StringBuilder(device.getLabel(context))
|
||||
if (device is HasPodStyle && item.showDebug) {
|
||||
val style = device.podStyle
|
||||
sb.append(" (${style.getColor(context)})")
|
||||
}
|
||||
text = sb
|
||||
|
||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||
else setTypeface(typeface, Typeface.NORMAL)
|
||||
|
||||
if (device is DualAirPods && item.showDebug) {
|
||||
append(" [${device.primaryPod.name}]")
|
||||
}
|
||||
}
|
||||
deviceIcon.setImageResource(device.iconRes)
|
||||
|
||||
lastSeen.text = context.getString(R.string.last_seen_x, device.lastSeenFormatted(item.now))
|
||||
firstSeen.text = context.getString(R.string.first_seen_x, device.firstSeenFormatted(item.now))
|
||||
firstSeen.isGone = Duration.between(device.seenFirstAt, device.seenLastAt).toMinutes() < 1
|
||||
|
||||
reception.text = item.getReceptionText()
|
||||
|
||||
// Pods battery state
|
||||
device.apply {
|
||||
podLeftBatteryIcon.setImageResource(getBatteryDrawable(batteryLeftPodPercent))
|
||||
podLeftBatteryLabel.text = getBatteryLevelLeftPod(context)
|
||||
|
||||
podRightBatteryIcon.setImageResource(getBatteryDrawable(batteryRightPodPercent))
|
||||
podRightBatteryLabel.text = getBatteryLevelRightPod(context)
|
||||
}
|
||||
|
||||
// Pods charging state
|
||||
device.apply {
|
||||
if (this is HasChargeDetectionDual) {
|
||||
podLeftChargingIcon.isInvisible = !isLeftPodCharging
|
||||
podLeftChargingLabel.isInvisible = !isLeftPodCharging
|
||||
|
||||
podRightChargingIcon.isInvisible = !isRightPodCharging
|
||||
podRightChargingLabel.isInvisible = !isRightPodCharging
|
||||
} else {
|
||||
podLeftChargingIcon.isGone = true
|
||||
podLeftChargingLabel.isGone = true
|
||||
|
||||
podRightChargingIcon.isGone = true
|
||||
podRightChargingLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Microphone state
|
||||
device.apply {
|
||||
if (this is HasDualMicrophone) {
|
||||
podLeftMicrophoneIcon.isInvisible = !isLeftPodMicrophone
|
||||
podLeftMicrophoneLabel.isInvisible = !isLeftPodMicrophone
|
||||
|
||||
podRightMicrophoneIcon.isInvisible = !isRightPodMicrophone
|
||||
podRightMicrophoneLabel.isInvisible = !isRightPodMicrophone
|
||||
} else {
|
||||
podLeftMicrophoneIcon.isGone = true
|
||||
podLeftMicrophoneLabel.isGone = true
|
||||
|
||||
podRightMicrophoneIcon.isGone = true
|
||||
podRightMicrophoneLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Pods wear state
|
||||
device.apply {
|
||||
if (this is HasEarDetectionDual) {
|
||||
podLeftWearIcon.isInvisible = !isLeftPodInEar
|
||||
podLeftWearLabel.isInvisible = !isLeftPodInEar
|
||||
|
||||
podRightWearIcon.isInvisible = !isRightPodInEar
|
||||
podRightWearLabel.isInvisible = !isRightPodInEar
|
||||
} else {
|
||||
podLeftWearIcon.isGone = true
|
||||
podLeftWearLabel.isGone = true
|
||||
|
||||
podRightWearIcon.isGone = true
|
||||
podRightWearLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Case charge state
|
||||
device.apply {
|
||||
if (this is HasCase) {
|
||||
podCaseBatteryIcon.isGone = false
|
||||
podCaseBatteryIcon.setImageResource(getBatteryDrawable(batteryCasePercent))
|
||||
podCaseBatteryLabel.text = getBatteryLevelCase(context)
|
||||
|
||||
podCaseChargingIcon.isInvisible = !isCaseCharging
|
||||
podCaseChargingLabel.isInvisible = !isCaseCharging
|
||||
} else {
|
||||
podCaseBatteryIcon.isGone = true
|
||||
podCaseBatteryLabel.isGone = true
|
||||
|
||||
podCaseChargingIcon.isGone = true
|
||||
podCaseChargingLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Case lid state
|
||||
device.apply {
|
||||
if (this is DualAirPods) {
|
||||
podCaseLidLabel.text = when (caseLidState) {
|
||||
LidState.OPEN -> context.getString(R.string.pods_case_status_open_label)
|
||||
LidState.CLOSED -> context.getString(R.string.pods_case_status_closed_label)
|
||||
else -> context.getString(R.string.pods_case_unknown_state)
|
||||
}
|
||||
|
||||
val hideInfo = !listOf(LidState.OPEN, LidState.CLOSED).contains(caseLidState)
|
||||
podCaseLidIcon.isInvisible = hideInfo
|
||||
podCaseLidLabel.isInvisible = hideInfo
|
||||
} else {
|
||||
podCaseLidIcon.isGone = true
|
||||
podCaseLidLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Connection state
|
||||
device.apply {
|
||||
val sb = StringBuilder()
|
||||
if (this is HasStateDetection) {
|
||||
sb.append(state.getLabel(context))
|
||||
}
|
||||
if (item.showDebug) {
|
||||
sb.append("\n\n").append("---Debug---")
|
||||
sb.append("\n").append(rawDataHex)
|
||||
}
|
||||
status.text = sb
|
||||
status.isGone = sb.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
data class Item(
|
||||
override val now: Instant,
|
||||
override val device: DualPodDevice,
|
||||
override val showDebug: Boolean,
|
||||
override val isMainPod: Boolean,
|
||||
) : PodDeviceVH.Item
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards.pods
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.BindableVH
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.common.lists.modular.ModularAdapter
|
||||
import eu.darken.capod.wear.ui.overview.OverviewAdapter
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.getSignalQuality
|
||||
import java.time.Instant
|
||||
|
||||
abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
|
||||
@LayoutRes layoutId: Int,
|
||||
parent: ViewGroup
|
||||
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B> {
|
||||
|
||||
fun Item.getReceptionText(): String = device.getSignalQuality(context)
|
||||
.let { if (showDebug) "$it ${device.seenCounter}" else it }
|
||||
.let { if (isMainPod) "$it\n(${getString(R.string.pods_yours)})" else it }
|
||||
|
||||
interface Item : OverviewAdapter.Item {
|
||||
|
||||
val now: Instant
|
||||
|
||||
val device: PodDevice
|
||||
|
||||
val showDebug: Boolean
|
||||
|
||||
val isMainPod: Boolean
|
||||
|
||||
override val stableId: Long get() = device.identifier.hashCode().toLong()
|
||||
|
||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)?
|
||||
get() = { old, new ->
|
||||
if (new::class.isInstance(old)) new else null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards.pods
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.databinding.OverviewPodsSingleItemBinding
|
||||
import eu.darken.capod.pods.core.*
|
||||
import java.time.Instant
|
||||
|
||||
class SinglePodsCardVH(parent: ViewGroup) :
|
||||
PodDeviceVH<SinglePodsCardVH.Item, OverviewPodsSingleItemBinding>(
|
||||
R.layout.overview_pods_single_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy { OverviewPodsSingleItemBinding.bind(itemView) }
|
||||
|
||||
override val onBindData = binding(payload = true) { item: Item ->
|
||||
val device = item.device
|
||||
|
||||
name.apply {
|
||||
text = device.getLabel(context)
|
||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||
else setTypeface(typeface, Typeface.NORMAL)
|
||||
}
|
||||
|
||||
deviceIcon.setImageResource(device.iconRes)
|
||||
|
||||
lastSeen.text = device.lastSeenFormatted(item.now)
|
||||
|
||||
reception.text = item.getReceptionText()
|
||||
|
||||
// Battery level
|
||||
device.apply {
|
||||
batteryLabel.text = getBatteryLevelHeadset(context)
|
||||
batteryIcon.setImageResource(getBatteryDrawable(batteryHeadsetPercent))
|
||||
}
|
||||
|
||||
// Charge state
|
||||
device.apply {
|
||||
if (this is HasChargeDetection) {
|
||||
chargingIcon.isInvisible = isHeadsetBeingCharged
|
||||
chargingLabel.isInvisible = isHeadsetBeingCharged
|
||||
} else {
|
||||
chargingIcon.isGone = true
|
||||
chargingLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
// Has ear detection
|
||||
device.apply {
|
||||
if (this is HasEarDetection) {
|
||||
wearIcon.isInvisible = isBeingWorn
|
||||
wearLabel.isInvisible = isBeingWorn
|
||||
} else {
|
||||
wearIcon.isGone = true
|
||||
wearLabel.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
status.apply {
|
||||
val sb = StringBuilder()
|
||||
if (item.showDebug) {
|
||||
sb.append("--- Debug ---")
|
||||
sb.append("\n").append(device.rawDataHex)
|
||||
}
|
||||
text = sb
|
||||
isGone = !item.showDebug
|
||||
}
|
||||
}
|
||||
|
||||
data class Item(
|
||||
override val now: Instant,
|
||||
override val device: SinglePodDevice,
|
||||
override val showDebug: Boolean,
|
||||
override val isMainPod: Boolean,
|
||||
) : PodDeviceVH.Item
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package eu.darken.capod.wear.ui.overview.cards.pods
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.view.ViewGroup
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.lastSeenFormatted
|
||||
import java.time.Instant
|
||||
|
||||
class UnknownPodDeviceCardVH(parent: ViewGroup) :
|
||||
PodDeviceVH<UnknownPodDeviceCardVH.Item, OverviewPodsUnknownItemBinding>(
|
||||
R.layout.overview_pods_unknown_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy {
|
||||
OverviewPodsUnknownItemBinding.bind(itemView)
|
||||
}
|
||||
|
||||
override val onBindData = binding(payload = true) { item ->
|
||||
val device = item.device
|
||||
name.apply {
|
||||
text = device.getLabel(context)
|
||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||
else setTypeface(typeface, Typeface.NORMAL)
|
||||
}
|
||||
|
||||
lastSeen.text = device.lastSeenFormatted(item.now)
|
||||
reception.text = item.getReceptionText()
|
||||
|
||||
details.text = when (item.device) {
|
||||
is ApplePods -> getString(R.string.pods_unknown_contact_dev)
|
||||
else -> getString(R.string.pods_unknown_label)
|
||||
}
|
||||
|
||||
rawdata.text = device.rawDataHex.joinToString("\n")
|
||||
}
|
||||
|
||||
data class Item(
|
||||
override val now: Instant,
|
||||
override val device: PodDevice,
|
||||
override val showDebug: Boolean = false,
|
||||
override val isMainPod: Boolean = false,
|
||||
) : PodDeviceVH.Item
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.wear.widget.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".wear.ui.MainActivity"
|
||||
tools:deviceIds="wear">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/inner_frame_layout_padding"
|
||||
app:layout_boxedEdges="all">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text"
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name" />
|
||||
|
||||
</FrameLayout>
|
||||
</androidx.wear.widget.BoxInsetLayout>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".wear.ui.MainActivity">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
app:startDestination="@id/overviewFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/overviewFragment"
|
||||
android:name="eu.darken.capod.wear.ui.overview.OverviewFragment"
|
||||
tools:layout="@layout/main_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_overviewFragment_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/settingsFragment"
|
||||
android:name="eu.darken.capod.main.ui.settings.SettingsFragment"
|
||||
android:label="SettingsFragment"
|
||||
tools:layout="@layout/settings_fragment" />
|
||||
|
||||
</navigation>
|
||||
@@ -0,0 +1,32 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
||||
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
|
||||
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
|
||||
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
|
||||
<item name="colorSecondary">@color/md_theme_dark_secondary</item>
|
||||
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
|
||||
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
|
||||
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item>
|
||||
<item name="colorTertiary">@color/md_theme_dark_tertiary</item>
|
||||
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
|
||||
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
|
||||
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item>
|
||||
<item name="colorError">@color/md_theme_dark_error</item>
|
||||
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
|
||||
<item name="colorOnError">@color/md_theme_dark_onError</item>
|
||||
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
|
||||
<item name="android:colorBackground">@color/md_theme_dark_background</item>
|
||||
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
|
||||
<item name="colorSurface">@color/md_theme_dark_surface</item>
|
||||
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
|
||||
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
|
||||
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
|
||||
<item name="colorOutline">@color/md_theme_dark_outline</item>
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
||||
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="md_theme_light_primary">#3F7AFF</color>
|
||||
<color name="md_theme_light_onPrimary">#FFFFFF</color>
|
||||
<color name="md_theme_light_primaryContainer">#DAE2FF</color>
|
||||
<color name="md_theme_light_onPrimaryContainer">#00174B</color>
|
||||
<color name="md_theme_light_secondary">#715C00</color>
|
||||
<color name="md_theme_light_onSecondary">#FFFFFF</color>
|
||||
<color name="md_theme_light_secondaryContainer">#FFE16C</color>
|
||||
<color name="md_theme_light_onSecondaryContainer">#231B00</color>
|
||||
<color name="md_theme_light_tertiary">#006D39</color>
|
||||
<color name="md_theme_light_onTertiary">#FFFFFF</color>
|
||||
<color name="md_theme_light_tertiaryContainer">#5CFFA0</color>
|
||||
<color name="md_theme_light_onTertiaryContainer">#00210D</color>
|
||||
<color name="md_theme_light_error">#BA1B1B</color>
|
||||
<color name="md_theme_light_errorContainer">#FFDAD4</color>
|
||||
<color name="md_theme_light_onError">#FFFFFF</color>
|
||||
<color name="md_theme_light_onErrorContainer">#410001</color>
|
||||
<color name="md_theme_light_background">#FEFBFF</color>
|
||||
<color name="md_theme_light_onBackground">#1B1B1F</color>
|
||||
<color name="md_theme_light_surface">#FEFBFF</color>
|
||||
<color name="md_theme_light_onSurface">#1B1B1F</color>
|
||||
<color name="md_theme_light_surfaceVariant">#E2E2EC</color>
|
||||
<color name="md_theme_light_onSurfaceVariant">#44464E</color>
|
||||
<color name="md_theme_light_outline">#75767F</color>
|
||||
<color name="md_theme_light_inverseOnSurface">#F2F0F5</color>
|
||||
<color name="md_theme_light_inverseSurface">#303033</color>
|
||||
<color name="md_theme_light_primaryInverse">#B1C5FF</color>
|
||||
|
||||
<color name="md_theme_dark_primary">#3F7AFF</color>
|
||||
<color name="md_theme_dark_onPrimary">#002A78</color>
|
||||
<color name="md_theme_dark_primaryContainer">#003EA7</color>
|
||||
<color name="md_theme_dark_onPrimaryContainer">#DAE2FF</color>
|
||||
<color name="md_theme_dark_secondary">#E9C426</color>
|
||||
<color name="md_theme_dark_onSecondary">#3B2F00</color>
|
||||
<color name="md_theme_dark_secondaryContainer">#564500</color>
|
||||
<color name="md_theme_dark_onSecondaryContainer">#FFE16C</color>
|
||||
<color name="md_theme_dark_tertiary">#33E286</color>
|
||||
<color name="md_theme_dark_onTertiary">#00391B</color>
|
||||
<color name="md_theme_dark_tertiaryContainer">#005229</color>
|
||||
<color name="md_theme_dark_onTertiaryContainer">#5CFFA0</color>
|
||||
<color name="md_theme_dark_error">#FFB4A9</color>
|
||||
<color name="md_theme_dark_errorContainer">#930006</color>
|
||||
<color name="md_theme_dark_onError">#680003</color>
|
||||
<color name="md_theme_dark_onErrorContainer">#FFDAD4</color>
|
||||
<color name="md_theme_dark_background">#1B1B1F</color>
|
||||
<color name="md_theme_dark_onBackground">#E3E1E6</color>
|
||||
<color name="md_theme_dark_surface">#1B1B1F</color>
|
||||
<color name="md_theme_dark_onSurface">#E3E1E6</color>
|
||||
<color name="md_theme_dark_surfaceVariant">#44464E</color>
|
||||
<color name="md_theme_dark_onSurfaceVariant">#C6C6D0</color>
|
||||
<color name="md_theme_dark_outline">#8F909A</color>
|
||||
<color name="md_theme_dark_inverseOnSurface">#1B1B1F</color>
|
||||
<color name="md_theme_dark_inverseSurface">#E3E1E6</color>
|
||||
<color name="md_theme_dark_primaryInverse">#0054D9</color>
|
||||
|
||||
<color name="seed">#3F7AFF</color>
|
||||
<color name="error">#BA1B1B</color>
|
||||
|
||||
<color name="brand_primary">#3f7aff</color>
|
||||
<color name="brand_secondary">#ffd83c</color>
|
||||
<color name="brand_tertiary">#41eb8e</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,48 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/md_theme_light_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
|
||||
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
|
||||
<item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item>
|
||||
<item name="colorSecondary">@color/md_theme_light_secondary</item>
|
||||
<item name="colorOnSecondary">@color/md_theme_light_onSecondary</item>
|
||||
<item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item>
|
||||
<item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item>
|
||||
<item name="colorTertiary">@color/md_theme_light_tertiary</item>
|
||||
<item name="colorOnTertiary">@color/md_theme_light_onTertiary</item>
|
||||
<item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item>
|
||||
<item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item>
|
||||
<item name="colorError">@color/md_theme_light_error</item>
|
||||
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
|
||||
<item name="colorOnError">@color/md_theme_light_onError</item>
|
||||
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
|
||||
<item name="android:colorBackground">@color/md_theme_light_background</item>
|
||||
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
|
||||
<item name="colorSurface">@color/md_theme_light_surface</item>
|
||||
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
|
||||
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
|
||||
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
|
||||
<item name="colorOutline">@color/md_theme_light_outline</item>
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
|
||||
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeFloating" parent="AppTheme">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeSplash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">#3f7aff</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_screen</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user