diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlockCardTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlockCardTest.kt
index 3cc2aaee73..323ddec06b 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlockCardTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlockCardTest.kt
@@ -17,236 +17,198 @@ class BlockCardTest {
private val testDate = "11/2/2022"
private val testTransactions = "2,175"
private val testSize = "1,606Kb"
+ private val testFees = "25 059 357"
private val testSource = "mempool.io"
@Test
fun testBlockCardWithAllElements() {
- // Arrange & Act
composeTestRule.setContent {
AppThemeSurface {
BlockCard(
- showWidgetTitle = true,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
+ showFees = true,
showSource = true,
block = testBlock,
time = testTime,
date = testDate,
transactions = testTransactions,
size = testSize,
- source = testSource
+ fees = testFees,
+ source = testSource,
)
}
}
- // Assert all elements exist
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_widget_title_icon", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_widget_title_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_block_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_time_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_date_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_transactions_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_size_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_source_row", useUnmergedTree = true).assertExists()
-
- // Verify text content
- composeTestRule.onNodeWithTag("block_card_block_text", useUnmergedTree = true).assertTextEquals(testBlock)
- composeTestRule.onNodeWithTag("block_card_time_text", useUnmergedTree = true).assertTextEquals(testTime)
- composeTestRule.onNodeWithTag("block_card_date_text", useUnmergedTree = true).assertTextEquals(testDate)
- composeTestRule.onNodeWithTag("block_card_transactions_text", useUnmergedTree = true).assertTextEquals(testTransactions)
- composeTestRule.onNodeWithTag("block_card_size_text", useUnmergedTree = true).assertTextEquals(testSize)
- composeTestRule.onNodeWithTag("block_card_source_text", useUnmergedTree = true).assertTextEquals(testSource)
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("time_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("date_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("transactions_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("size_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("fees_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("source_row", useUnmergedTree = true).assertExists()
+
+ composeTestRule.onNodeWithTag("block_text", useUnmergedTree = true).assertTextEquals(testBlock)
+ composeTestRule.onNodeWithTag("time_text", useUnmergedTree = true).assertTextEquals(testTime)
+ composeTestRule.onNodeWithTag("date_text", useUnmergedTree = true).assertTextEquals(testDate)
+ composeTestRule.onNodeWithTag("transactions_text", useUnmergedTree = true).assertTextEquals(testTransactions)
+ composeTestRule.onNodeWithTag("size_text", useUnmergedTree = true).assertTextEquals(testSize)
+ composeTestRule.onNodeWithTag("fees_text", useUnmergedTree = true).assertTextEquals(testFees)
+ composeTestRule.onNodeWithTag("source_text", useUnmergedTree = true).assertTextEquals(testSource)
}
@Test
- fun testBlockCardWithoutWidgetTitle() {
- // Arrange & Act
+ fun testBlockCardWithoutSource() {
composeTestRule.setContent {
AppThemeSurface {
BlockCard(
- showWidgetTitle = false,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
- showSource = true,
+ showFees = true,
+ showSource = false,
block = testBlock,
time = testTime,
date = testDate,
transactions = testTransactions,
size = testSize,
- source = testSource
+ fees = testFees,
+ source = testSource,
)
}
}
- // Assert main elements exist
- composeTestRule.onNodeWithTag("block_card_block_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_source_row", useUnmergedTree = true).assertExists()
-
- // Assert widget title elements do not exist
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_widget_title_icon", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_widget_title_text", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("source_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("source_text", useUnmergedTree = true).assertDoesNotExist()
}
@Test
- fun testBlockCardWithoutSource() {
- // Arrange & Act
+ fun testBlockCardWithoutFees() {
composeTestRule.setContent {
AppThemeSurface {
BlockCard(
- showWidgetTitle = true,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
- showSource = false,
+ showFees = false,
+ showSource = true,
block = testBlock,
time = testTime,
date = testDate,
transactions = testTransactions,
size = testSize,
- source = testSource
+ fees = testFees,
+ source = testSource,
)
}
}
- // Assert main elements exist
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_block_row", useUnmergedTree = true).assertExists()
-
- // Assert source elements do not exist
- composeTestRule.onNodeWithTag("block_card_source_row", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_source_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_source_text", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("fees_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("fees_text", useUnmergedTree = true).assertDoesNotExist()
}
@Test
fun testBlockCardMinimal() {
- // Arrange & Act - Only block number shown
composeTestRule.setContent {
AppThemeSurface {
BlockCard(
- showWidgetTitle = false,
showBlock = true,
showTime = false,
showDate = false,
showTransactions = false,
showSize = false,
+ showFees = false,
showSource = false,
block = testBlock,
time = "",
date = "",
transactions = "",
size = "",
- source = ""
+ fees = "",
+ source = "",
)
}
}
- // Assert only block row exists
- composeTestRule.onNodeWithTag("block_card_block_row", useUnmergedTree = true).assertExists()
-
- // Assert other elements do not exist
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_time_row", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_source_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("time_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("date_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("transactions_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("size_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("fees_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("source_row", useUnmergedTree = true).assertDoesNotExist()
}
@Test
fun testBlockCardWithEmptyValues() {
- // Arrange & Act
composeTestRule.setContent {
AppThemeSurface {
BlockCard(
- showWidgetTitle = true,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
+ showFees = true,
showSource = true,
block = "",
time = "",
date = "",
transactions = "",
size = "",
- source = ""
+ fees = "",
+ source = "",
)
}
}
- // Assert only widget title and labels exist (since values are empty)
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertExists()
-
- // Assert text elements don't exist (since values are empty)
-
- composeTestRule.onNodeWithTag("block_card_time_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_date_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_transactions_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_size_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_source_label", useUnmergedTree = true).assertDoesNotExist()
-
- composeTestRule.onNodeWithTag("block_card_block_label", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_block_text", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_time_text", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_date_text", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_transactions_text", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_size_text", useUnmergedTree = true).assertDoesNotExist()
- composeTestRule.onNodeWithTag("block_card_source_text", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("time_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("date_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("transactions_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("size_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("fees_row", useUnmergedTree = true).assertDoesNotExist()
+ composeTestRule.onNodeWithTag("source_row", useUnmergedTree = true).assertDoesNotExist()
}
@Test
- fun testAllElementsExistInFullConfiguration() {
- // Arrange & Act
+ fun testBlockCardSmallWithAllElements() {
composeTestRule.setContent {
AppThemeSurface {
- BlockCard(
- showWidgetTitle = true,
+ BlockCardSmall(
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
+ showFees = true,
showSource = true,
block = testBlock,
time = testTime,
date = testDate,
transactions = testTransactions,
size = testSize,
- source = testSource
+ fees = testFees,
+ source = testSource,
)
}
}
- // Assert all tagged elements exist
- composeTestRule.onNodeWithTag("block_card_widget_title_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_widget_title_icon", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_widget_title_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_block_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_block_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_block_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_time_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_time_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_time_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_date_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_date_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_date_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_transactions_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_transactions_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_transactions_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_size_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_size_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_size_text", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_source_row", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_source_label", useUnmergedTree = true).assertExists()
- composeTestRule.onNodeWithTag("block_card_source_text", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("block_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("time_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("date_row", useUnmergedTree = true).assertExists()
+ composeTestRule.onNodeWithTag("transactions_row", useUnmergedTree = true).assertExists()
+
+ composeTestRule.onNodeWithTag("block_text", useUnmergedTree = true).assertTextEquals(testBlock)
+ composeTestRule.onNodeWithTag("time_text", useUnmergedTree = true).assertTextEquals(testTime)
}
}
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
index 906a21ebaf..7f82d68f3f 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreenTest.kt
@@ -22,7 +22,8 @@ class BlocksEditScreenTest {
date = "11/2/2022",
transactionCount = "2,175",
size = "1,606Kb",
- source = "mempool.io"
+ source = "mempool.io",
+ fees = "25 059 357",
)
private val defaultPreferences = BlocksPreferences()
@@ -36,6 +37,7 @@ class BlocksEditScreenTest {
var dateClicked = false
var transactionsClicked = false
var sizeClicked = false
+ var feesClicked = false
var sourceClicked = false
var resetClicked = false
var previewClicked = false
@@ -50,6 +52,7 @@ class BlocksEditScreenTest {
onClickShowDate = { dateClicked = true },
onClickShowTransactions = { transactionsClicked = true },
onClickShowSize = { sizeClicked = true },
+ onClickShowFees = { feesClicked = true },
onClickShowSource = { sourceClicked = true },
onClickReset = { resetClicked = true },
onClickPreview = { previewClicked = true },
@@ -63,13 +66,14 @@ class BlocksEditScreenTest {
composeTestRule.onNodeWithTag("blocks_edit_screen").assertExists()
composeTestRule.onNodeWithTag("WidgetEditScrollView").assertExists()
- // Verify description
- composeTestRule.onNodeWithTag("edit_description").assertExists()
+ // Verify section header
+ composeTestRule.onNodeWithTag("data_section_header").assertExists()
// Verify all setting rows exist
- listOf("block", "time", "date", "transactions", "size", "source").forEach { prefix ->
+ listOf("block", "time", "date", "transactions", "size", "fees", "source").forEach { prefix ->
composeTestRule.onNodeWithTag("${prefix}_setting_row").assertExists()
composeTestRule.onNodeWithTag("${prefix}_label").assertExists()
+ composeTestRule.onNodeWithTag("${prefix}_leading_icon", useUnmergedTree = true).assertExists()
if (testBlock.getFieldValue(prefix).isNotEmpty()) {
composeTestRule.onNodeWithTag("${prefix}_text").assertExists()
}
@@ -115,6 +119,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = { resetClicked = true },
onClickPreview = {},
@@ -146,6 +151,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -167,6 +173,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = false,
+ showFees = false,
showSource = false
)
@@ -179,6 +186,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -199,6 +207,7 @@ class BlocksEditScreenTest {
var dateClicked = false
var transactionsClicked = false
var sizeClicked = false
+ var feesClicked = false
var sourceClicked = false
var resetClicked = false
var previewClicked = false
@@ -209,6 +218,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = true,
+ showFees = false,
showSource = true
)
@@ -221,6 +231,7 @@ class BlocksEditScreenTest {
onClickShowDate = { dateClicked = true },
onClickShowTransactions = { transactionsClicked = true },
onClickShowSize = { sizeClicked = true },
+ onClickShowFees = { feesClicked = true },
onClickShowSource = { sourceClicked = true },
onClickReset = { resetClicked = true },
onClickPreview = { previewClicked = true },
@@ -246,6 +257,9 @@ class BlocksEditScreenTest {
composeTestRule.onNodeWithTag("size_toggle_button").performClick()
assert(sizeClicked)
+ composeTestRule.onNodeWithTag("fees_toggle_button").performClick()
+ assert(feesClicked)
+
composeTestRule.onNodeWithTag("source_toggle_button").performClick()
assert(sourceClicked)
@@ -265,7 +279,8 @@ class BlocksEditScreenTest {
date = "",
transactionCount = "",
size = "",
- source = ""
+ source = "",
+ fees = "",
)
composeTestRule.setContent {
@@ -277,6 +292,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -287,7 +303,7 @@ class BlocksEditScreenTest {
}
// Assert that text elements don't exist when values are empty
- listOf("block", "time", "date", "transactions", "size", "source").forEach { prefix ->
+ listOf("block", "time", "date", "transactions", "size", "fees", "source").forEach { prefix ->
composeTestRule.onNodeWithTag("${prefix}_text").assertDoesNotExist()
}
}
@@ -304,6 +320,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -313,6 +330,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = true,
+ showFees = false,
showSource = true
),
block = testBlock
@@ -323,11 +341,12 @@ class BlocksEditScreenTest {
// Assert all tagged elements exist
composeTestRule.onNodeWithTag("blocks_edit_screen").assertExists()
composeTestRule.onNodeWithTag("WidgetEditScrollView").assertExists()
- composeTestRule.onNodeWithTag("edit_description").assertExists()
+ composeTestRule.onNodeWithTag("data_section_header").assertExists()
- listOf("block", "time", "date", "transactions", "size", "source").forEach { prefix ->
+ listOf("block", "time", "date", "transactions", "size", "fees", "source").forEach { prefix ->
composeTestRule.onNodeWithTag("${prefix}_setting_row").assertExists()
composeTestRule.onNodeWithTag("${prefix}_label").assertExists()
+ composeTestRule.onNodeWithTag("${prefix}_leading_icon", useUnmergedTree = true).assertExists()
composeTestRule.onNodeWithTag("${prefix}_toggle_button").assertExists()
composeTestRule.onNodeWithTag("${prefix}_toggle_icon", useUnmergedTree = true).assertExists()
composeTestRule.onNodeWithTag("${prefix}_divider").assertExists()
@@ -347,6 +366,7 @@ private fun BlockModel.getFieldValue(prefix: String): String {
"date" -> date
"transactions" -> transactionCount
"size" -> size
+ "fees" -> fees
"source" -> source
else -> ""
}
diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
index 3a5393d50e..e85b589ab5 100644
--- a/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
+++ b/app/src/androidTest/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreenTest.kt
@@ -21,7 +21,8 @@ class BlocksPreviewContentTest {
date = "2023-01-01",
transactionCount = "2,175",
size = "1,606kB",
- source = "mempool.space"
+ source = "mempool.space",
+ fees = "25 059 357",
)
private val defaultPreferences = BlocksPreferences()
@@ -41,7 +42,6 @@ class BlocksPreviewContentTest {
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
onClickSave = { saveClicked = true },
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
@@ -96,7 +96,6 @@ class BlocksPreviewContentTest {
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
onClickSave = { saveClicked = true },
- showWidgetTitles = false,
isBlocksWidgetEnabled = false,
blocksPreferences = defaultPreferences,
block = testBlock
@@ -137,7 +136,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = customPreferences,
block = testBlock
@@ -161,7 +159,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
@@ -197,7 +194,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
@@ -228,7 +224,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = false,
isBlocksWidgetEnabled = false,
blocksPreferences = minimalPreferences,
block = testBlock
@@ -253,7 +248,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
@@ -285,7 +279,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = customPreferences,
block = testBlock
@@ -307,7 +300,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
- showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = null
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b02cdd87b..74291452ae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -217,6 +217,19 @@
android:resource="@xml/appwidget_info_headlines" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/to/bitkit/appwidget/AppWidgetDataRepository.kt b/app/src/main/java/to/bitkit/appwidget/AppWidgetDataRepository.kt
index 40b5e9c6ee..be69cdb4ae 100644
--- a/app/src/main/java/to/bitkit/appwidget/AppWidgetDataRepository.kt
+++ b/app/src/main/java/to/bitkit/appwidget/AppWidgetDataRepository.kt
@@ -3,8 +3,10 @@ package to.bitkit.appwidget
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import to.bitkit.data.dto.ArticleDTO
+import to.bitkit.data.dto.BlockDTO
import to.bitkit.data.dto.price.GraphPeriod
import to.bitkit.data.dto.price.PriceDTO
+import to.bitkit.data.widgets.BlocksService
import to.bitkit.data.widgets.NewsService
import to.bitkit.data.widgets.PriceService
import to.bitkit.di.IoDispatcher
@@ -16,6 +18,7 @@ class AppWidgetDataRepository @Inject constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val priceService: PriceService,
private val newsService: NewsService,
+ private val blocksService: BlocksService,
) {
suspend fun fetchPriceData(period: GraphPeriod = GraphPeriod.ONE_DAY): Result =
withContext(ioDispatcher) {
@@ -26,4 +29,9 @@ class AppWidgetDataRepository @Inject constructor(
withContext(ioDispatcher) {
newsService.fetchData()
}
+
+ suspend fun fetchBlock(): Result =
+ withContext(ioDispatcher) {
+ blocksService.fetchData()
+ }
}
diff --git a/app/src/main/java/to/bitkit/appwidget/AppWidgetPreferencesStore.kt b/app/src/main/java/to/bitkit/appwidget/AppWidgetPreferencesStore.kt
index c2692c2cf4..5c089c4c38 100644
--- a/app/src/main/java/to/bitkit/appwidget/AppWidgetPreferencesStore.kt
+++ b/app/src/main/java/to/bitkit/appwidget/AppWidgetPreferencesStore.kt
@@ -14,6 +14,7 @@ import to.bitkit.appwidget.model.AppWidgetData
import to.bitkit.appwidget.model.AppWidgetEntry
import to.bitkit.appwidget.model.AppWidgetType
import to.bitkit.data.dto.ArticleDTO
+import to.bitkit.data.dto.BlockDTO
import to.bitkit.data.dto.price.GraphPeriod
import to.bitkit.data.dto.price.PriceDTO
import to.bitkit.data.serializers.AppWidgetDataSerializer
@@ -32,6 +33,7 @@ interface AppWidgetEntryPoint {
}
@Singleton
+@Suppress("TooManyFunctions")
class AppWidgetPreferencesStore @Inject constructor(
@ApplicationContext private val context: Context,
) {
@@ -90,4 +92,8 @@ class AppWidgetPreferencesStore @Inject constructor(
)
}
}
+
+ suspend fun cacheBlock(block: BlockDTO) {
+ store.updateData { it.copy(cachedBlock = block) }
+ }
}
diff --git a/app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt b/app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt
index 87166d4050..03ce70786f 100644
--- a/app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt
+++ b/app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt
@@ -16,6 +16,8 @@ import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import to.bitkit.appwidget.model.AppWidgetType
+import to.bitkit.appwidget.ui.blocks.BlocksGlanceReceiver
+import to.bitkit.appwidget.ui.blocks.BlocksGlanceWidget
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceReceiver
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceWidget
import to.bitkit.appwidget.ui.price.PriceGlanceReceiver
@@ -65,6 +67,7 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
private fun receiverClassFor(type: AppWidgetType): Class = when (type) {
AppWidgetType.PRICE -> PriceGlanceReceiver::class.java
AppWidgetType.HEADLINES -> HeadlinesGlanceReceiver::class.java
+ AppWidgetType.BLOCKS -> BlocksGlanceReceiver::class.java
}
}
@@ -96,6 +99,15 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
}
HeadlinesGlanceWidget().updateAll(appContext)
}
+
+ AppWidgetType.BLOCKS -> {
+ dataRepository.fetchBlock()
+ .onSuccess { preferencesStore.cacheBlock(it) }
+ .onFailure {
+ Logger.warn("Failed to refresh block", it, context = TAG)
+ }
+ BlocksGlanceWidget().updateAll(appContext)
+ }
}
}
diff --git a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigActivity.kt b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigActivity.kt
index 8cf58ef5d4..8742e48a80 100644
--- a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigActivity.kt
+++ b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigActivity.kt
@@ -11,6 +11,8 @@ import androidx.glance.appwidget.updateAll
import dagger.hilt.android.AndroidEntryPoint
import to.bitkit.appwidget.AppWidgetRefreshWorker
import to.bitkit.appwidget.model.AppWidgetType
+import to.bitkit.appwidget.ui.blocks.BlocksGlanceReceiver
+import to.bitkit.appwidget.ui.blocks.BlocksGlanceWidget
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceReceiver
import to.bitkit.appwidget.ui.headlines.HeadlinesGlanceWidget
import to.bitkit.appwidget.ui.price.PriceGlanceReceiver
@@ -55,6 +57,7 @@ class AppWidgetConfigActivity : ComponentActivity() {
when (viewModel.uiState.value.type) {
AppWidgetType.PRICE -> PriceGlanceWidget().updateAll(this@AppWidgetConfigActivity)
AppWidgetType.HEADLINES -> HeadlinesGlanceWidget().updateAll(this@AppWidgetConfigActivity)
+ AppWidgetType.BLOCKS -> BlocksGlanceWidget().updateAll(this@AppWidgetConfigActivity)
}
AppWidgetRefreshWorker.enqueue(this@AppWidgetConfigActivity)
val result = Intent().putExtra(
@@ -80,6 +83,7 @@ class AppWidgetConfigActivity : ComponentActivity() {
return when (providerClass) {
HeadlinesGlanceReceiver::class.java.name -> AppWidgetType.HEADLINES
PriceGlanceReceiver::class.java.name -> AppWidgetType.PRICE
+ BlocksGlanceReceiver::class.java.name -> AppWidgetType.BLOCKS
else -> {
Logger.warn(
"Encountered unknown provider class '$providerClass' " +
diff --git a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt
index 85661cd1b6..f4da53dc77 100644
--- a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt
+++ b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigScreen.kt
@@ -1,44 +1,9 @@
package to.bitkit.appwidget.config
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import to.bitkit.R
import to.bitkit.appwidget.model.AppWidgetType
-import to.bitkit.data.dto.price.GraphPeriod
-import to.bitkit.data.dto.price.TradingPair
-import to.bitkit.ext.label
-import to.bitkit.models.widget.ArticleModel
-import to.bitkit.models.widget.HeadlinePreferences
-import to.bitkit.models.widget.PricePreferences
-import to.bitkit.ui.components.BodySSB
-import to.bitkit.ui.components.Caption13Up
-import to.bitkit.ui.components.PrimaryButton
-import to.bitkit.ui.components.SecondaryButton
-import to.bitkit.ui.components.Title
-import to.bitkit.ui.components.VerticalSpacer
-import to.bitkit.ui.scaffold.AppTopBar
-import to.bitkit.ui.scaffold.ScreenColumn
-import to.bitkit.ui.theme.Colors
@Composable
fun AppWidgetConfigScreen(
@@ -49,7 +14,7 @@ fun AppWidgetConfigScreen(
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (state.type) {
- AppWidgetType.PRICE -> Content(
+ AppWidgetType.PRICE -> PriceConfigContent(
state = state,
onSelectPair = { viewModel.selectPricePair(it) },
onSelectPeriod = { viewModel.selectPricePeriod(it) },
@@ -66,257 +31,19 @@ fun AppWidgetConfigScreen(
onSave = { viewModel.saveAndFinish(onConfirm) },
onCancel = onCancel,
)
- }
-}
-
-@Composable
-private fun Content(
- state: AppWidgetConfigUiState,
- onSelectPair: (TradingPair) -> Unit,
- onSelectPeriod: (GraphPeriod) -> Unit,
- onReset: () -> Unit,
- onSave: () -> Unit,
- onCancel: () -> Unit,
-) {
- val prefs = state.pricePreferences
- val selectedPair = prefs.enabledPairs.firstOrNull() ?: TradingPair.BTC_USD
-
- ScreenColumn(
- noBackground = true,
- modifier = Modifier.background(Colors.Gray7)
- ) {
- AppTopBar(
- titleText = stringResource(R.string.widgets__price__name),
- onBackClick = onCancel,
- )
-
- Column(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .weight(1f)
- .verticalScroll(rememberScrollState())
- ) {
- VerticalSpacer(16.dp)
-
- Caption13Up(
- text = stringResource(R.string.appwidget__price__currency),
- color = Colors.White64,
- modifier = Modifier.padding(bottom = 16.dp)
- )
-
- for (pair in TradingPair.entries) {
- SelectableRow(
- label = pair.displayName,
- isSelected = pair == selectedPair,
- onClick = { onSelectPair(pair) },
- )
- }
-
- VerticalSpacer(16.dp)
- Caption13Up(
- text = stringResource(R.string.appwidget__price__timeframe),
- color = Colors.White64,
- modifier = Modifier.padding(vertical = 16.dp)
- )
-
- for (period in GraphPeriod.entries) {
- SelectableRow(
- label = period.label(),
- isSelected = period == prefs.period,
- onClick = { onSelectPeriod(period) },
- )
- }
- }
-
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth()
- ) {
- SecondaryButton(
- text = stringResource(R.string.common__reset),
- enabled = prefs != PricePreferences(),
- fullWidth = false,
- onClick = onReset,
- modifier = Modifier.weight(1f)
- )
- PrimaryButton(
- text = stringResource(R.string.common__save),
- isLoading = state.isSaving,
- enabled = !state.isSaving,
- fullWidth = false,
- onClick = onSave,
- modifier = Modifier.weight(1f)
- )
- }
- }
-}
-
-@Composable
-private fun HeadlinesConfigContent(
- state: AppWidgetConfigUiState,
- onToggleSource: () -> Unit,
- onToggleTime: () -> Unit,
- onReset: () -> Unit,
- onSave: () -> Unit,
- onCancel: () -> Unit,
-) {
- val prefs = state.headlinePreferences
- val previewArticle = ArticleModel(
- title = stringResource(R.string.widgets__headline__preview_title),
- timeAgo = stringResource(R.string.widgets__headline__preview_time),
- publisher = stringResource(R.string.widgets__headline__preview_publisher),
- link = "",
- )
- ScreenColumn(
- noBackground = true,
- modifier = Modifier.background(Colors.Gray7)
- ) {
- AppTopBar(
- titleText = stringResource(R.string.widgets__news__name),
- onBackClick = onCancel,
+ AppWidgetType.BLOCKS -> BlocksConfigContent(
+ state = state,
+ onToggleBlock = { viewModel.toggleBlockShowBlock() },
+ onToggleTime = { viewModel.toggleBlockShowTime() },
+ onToggleDate = { viewModel.toggleBlockShowDate() },
+ onToggleTransactions = { viewModel.toggleBlockShowTransactions() },
+ onToggleSize = { viewModel.toggleBlockShowSize() },
+ onToggleFees = { viewModel.toggleBlockShowFees() },
+ onToggleSource = { viewModel.toggleBlockShowSource() },
+ onReset = { viewModel.resetPreferences() },
+ onSave = { viewModel.saveAndFinish(onConfirm) },
+ onCancel = onCancel,
)
-
- Column(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .weight(1f)
- .verticalScroll(rememberScrollState())
- ) {
- VerticalSpacer(16.dp)
-
- Caption13Up(
- text = stringResource(R.string.widgets__widget__content),
- color = Colors.White64,
- modifier = Modifier.padding(bottom = 16.dp)
- )
-
- ToggleRow(
- content = {
- Title(
- text = previewArticle.title,
- modifier = Modifier.weight(1f)
- )
- },
- isEnabled = true,
- onToggle = {},
- toggleEnabled = false,
- )
- HorizontalDivider()
-
- ToggleRow(
- content = {
- BodySSB(
- text = previewArticle.publisher,
- color = Colors.Brand,
- modifier = Modifier.weight(1f)
- )
- },
- isEnabled = prefs.showSource,
- onToggle = onToggleSource,
- )
- HorizontalDivider()
-
- ToggleRow(
- content = {
- BodySSB(
- text = previewArticle.timeAgo,
- color = Colors.White64,
- modifier = Modifier.weight(1f)
- )
- },
- isEnabled = prefs.showTime,
- onToggle = onToggleTime,
- )
- HorizontalDivider()
- }
-
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth()
- ) {
- SecondaryButton(
- text = stringResource(R.string.common__reset),
- enabled = prefs != HeadlinePreferences(),
- fullWidth = false,
- onClick = onReset,
- modifier = Modifier.weight(1f)
- )
- PrimaryButton(
- text = stringResource(R.string.common__save),
- isLoading = state.isSaving,
- enabled = !state.isSaving,
- fullWidth = false,
- onClick = onSave,
- modifier = Modifier.weight(1f)
- )
- }
- }
-}
-
-@Composable
-private fun ToggleRow(
- content: @Composable RowScope.() -> Unit,
- isEnabled: Boolean,
- onToggle: () -> Unit,
- modifier: Modifier = Modifier,
- toggleEnabled: Boolean = true,
-) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- modifier = modifier
- .padding(vertical = 8.dp)
- .fillMaxWidth()
- ) {
- content()
- IconButton(
- onClick = onToggle,
- enabled = toggleEnabled,
- ) {
- Icon(
- painter = painterResource(R.drawable.ic_checkmark),
- contentDescription = null,
- tint = if (isEnabled) Colors.Brand else Colors.White50,
- modifier = Modifier.size(32.dp)
- )
- }
- }
-}
-
-@Composable
-private fun SelectableRow(
- label: String,
- isSelected: Boolean,
- onClick: () -> Unit,
-) {
- Column {
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = onClick)
- .padding(vertical = 14.dp)
- ) {
- BodySSB(
- text = label,
- color = if (isSelected) Colors.White else Colors.White64,
- modifier = Modifier.weight(1f)
- )
- if (isSelected) {
- Icon(
- painter = painterResource(R.drawable.ic_checkmark),
- contentDescription = null,
- tint = Colors.Brand,
- modifier = Modifier.size(32.dp)
- )
- }
- }
- HorizontalDivider()
}
}
diff --git a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt
index 887403b103..bd38942e5f 100644
--- a/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt
+++ b/app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt
@@ -8,21 +8,27 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import to.bitkit.appwidget.AppWidgetDataRepository
import to.bitkit.appwidget.AppWidgetPreferencesStore
import to.bitkit.appwidget.model.AppWidgetType
+import to.bitkit.appwidget.model.HomeBlocksPreferences
import to.bitkit.appwidget.model.HomeHeadlinePreferences
import to.bitkit.appwidget.model.HomePricePreferences
import to.bitkit.data.dto.price.GraphPeriod
import to.bitkit.data.dto.price.TradingPair
+import to.bitkit.models.widget.ArticleModel
+import to.bitkit.models.widget.BlocksPreferences
import to.bitkit.models.widget.HeadlinePreferences
import to.bitkit.models.widget.PricePreferences
+import to.bitkit.models.widget.toArticleModel
import to.bitkit.utils.Logger
import javax.inject.Inject
@HiltViewModel
+@Suppress("TooManyFunctions")
class AppWidgetConfigViewModel @Inject constructor(
private val preferencesStore: AppWidgetPreferencesStore,
private val dataRepository: AppWidgetDataRepository,
@@ -38,6 +44,8 @@ class AppWidgetConfigViewModel @Inject constructor(
fun init(appWidgetId: Int, type: AppWidgetType) {
viewModelScope.launch {
val entry = preferencesStore.getEntry(appWidgetId)
+ val cachedArticles = preferencesStore.data.first().cachedArticles
+ val previewArticle = cachedArticles.randomOrNull()?.toArticleModel() ?: DEFAULT_PREVIEW_ARTICLE
_uiState.update {
it.copy(
@@ -45,6 +53,8 @@ class AppWidgetConfigViewModel @Inject constructor(
type = type,
pricePreferences = entry?.pricePreferences?.toInApp() ?: PricePreferences(),
headlinePreferences = entry?.headlinePreferences?.toInApp() ?: HeadlinePreferences(),
+ blocksPreferences = entry?.blocksPreferences?.toInApp() ?: BlocksPreferences(),
+ previewArticle = previewArticle,
)
}
}
@@ -78,11 +88,58 @@ class AppWidgetConfigViewModel @Inject constructor(
}
}
+ fun toggleBlockShowBlock() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showBlock = !it.blocksPreferences.showBlock))
+ }
+ }
+
+ fun toggleBlockShowTime() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showTime = !it.blocksPreferences.showTime))
+ }
+ }
+
+ fun toggleBlockShowDate() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showDate = !it.blocksPreferences.showDate))
+ }
+ }
+
+ fun toggleBlockShowTransactions() {
+ _uiState.update {
+ it.copy(
+ blocksPreferences = it.blocksPreferences.copy(
+ showTransactions = !it.blocksPreferences.showTransactions,
+ ),
+ )
+ }
+ }
+
+ fun toggleBlockShowSize() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showSize = !it.blocksPreferences.showSize))
+ }
+ }
+
+ fun toggleBlockShowFees() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showFees = !it.blocksPreferences.showFees))
+ }
+ }
+
+ fun toggleBlockShowSource() {
+ _uiState.update {
+ it.copy(blocksPreferences = it.blocksPreferences.copy(showSource = !it.blocksPreferences.showSource))
+ }
+ }
+
fun resetPreferences() {
_uiState.update {
when (it.type) {
AppWidgetType.PRICE -> it.copy(pricePreferences = PricePreferences())
AppWidgetType.HEADLINES -> it.copy(headlinePreferences = HeadlinePreferences())
+ AppWidgetType.BLOCKS -> it.copy(blocksPreferences = BlocksPreferences())
}
}
}
@@ -95,6 +152,7 @@ class AppWidgetConfigViewModel @Inject constructor(
when (state.type) {
AppWidgetType.PRICE -> savePrice(state)
AppWidgetType.HEADLINES -> saveHeadlines(state)
+ AppWidgetType.BLOCKS -> saveBlocks(state)
}
onComplete()
@@ -126,14 +184,35 @@ class AppWidgetConfigViewModel @Inject constructor(
.onSuccess { preferencesStore.cacheArticlesAndRotate(it) }
.onFailure { Logger.warn("Failed to fetch initial articles", it, context = TAG) }
}
+
+ private suspend fun saveBlocks(state: AppWidgetConfigUiState) {
+ val appWidgetId = state.appWidgetId
+ val blocksPreferences = state.blocksPreferences
+ preferencesStore.registerWidget(appWidgetId, AppWidgetType.BLOCKS)
+ preferencesStore.updateEntry(appWidgetId) { entry ->
+ entry.copy(blocksPreferences = blocksPreferences.toHome())
+ }
+ dataRepository.fetchBlock()
+ .onSuccess { preferencesStore.cacheBlock(it) }
+ .onFailure { Logger.warn("Failed to fetch initial block", it, context = TAG) }
+ }
}
+private val DEFAULT_PREVIEW_ARTICLE = ArticleModel(
+ title = "How Bitcoin changed El Salvador in more ways",
+ timeAgo = "21 minutes ago",
+ publisher = "bitcoinmagazine.com",
+ link = "",
+)
+
@Stable
data class AppWidgetConfigUiState(
val appWidgetId: Int = -1,
val type: AppWidgetType = AppWidgetType.PRICE,
val pricePreferences: PricePreferences = PricePreferences(),
val headlinePreferences: HeadlinePreferences = HeadlinePreferences(),
+ val blocksPreferences: BlocksPreferences = BlocksPreferences(),
+ val previewArticle: ArticleModel = DEFAULT_PREVIEW_ARTICLE,
val isSaving: Boolean = false,
)
@@ -156,3 +235,23 @@ private fun HeadlinePreferences.toHome() = HomeHeadlinePreferences(
showTime = showTime,
showSource = showSource,
)
+
+private fun HomeBlocksPreferences.toInApp() = BlocksPreferences(
+ showBlock = showBlock,
+ showTime = showTime,
+ showDate = showDate,
+ showTransactions = showTransactions,
+ showSize = showSize,
+ showFees = showFees,
+ showSource = showSource,
+)
+
+private fun BlocksPreferences.toHome() = HomeBlocksPreferences(
+ showBlock = showBlock,
+ showTime = showTime,
+ showDate = showDate,
+ showTransactions = showTransactions,
+ showSize = showSize,
+ showFees = showFees,
+ showSource = showSource,
+)
diff --git a/app/src/main/java/to/bitkit/appwidget/config/BlocksConfigContent.kt b/app/src/main/java/to/bitkit/appwidget/config/BlocksConfigContent.kt
new file mode 100644
index 0000000000..70a257e024
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/config/BlocksConfigContent.kt
@@ -0,0 +1,208 @@
+package to.bitkit.appwidget.config
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import to.bitkit.R
+import to.bitkit.models.widget.BlockModel
+import to.bitkit.models.widget.BlocksPreferences
+import to.bitkit.ui.components.BodyM
+import to.bitkit.ui.components.BodySSB
+import to.bitkit.ui.components.Caption13Up
+import to.bitkit.ui.components.PrimaryButton
+import to.bitkit.ui.components.SecondaryButton
+import to.bitkit.ui.components.VerticalSpacer
+import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.scaffold.ScreenColumn
+import to.bitkit.ui.theme.Colors
+
+@Suppress("LongParameterList")
+@Composable
+internal fun BlocksConfigContent(
+ state: AppWidgetConfigUiState,
+ onToggleBlock: () -> Unit,
+ onToggleTime: () -> Unit,
+ onToggleDate: () -> Unit,
+ onToggleTransactions: () -> Unit,
+ onToggleSize: () -> Unit,
+ onToggleFees: () -> Unit,
+ onToggleSource: () -> Unit,
+ onReset: () -> Unit,
+ onSave: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ val prefs = state.blocksPreferences
+ val previewBlock = remember {
+ BlockModel(
+ height = "761,405",
+ time = "01:31:42 UTC",
+ date = "11/2/2022",
+ transactionCount = "2,175",
+ size = "1,606 Kb",
+ source = "mempool.io",
+ fees = "25 059 357",
+ )
+ }
+
+ ScreenColumn(
+ noBackground = true,
+ modifier = Modifier.background(Colors.Gray7)
+ ) {
+ AppTopBar(
+ titleText = stringResource(R.string.widgets__blocks__name),
+ onBackClick = onCancel,
+ )
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ VerticalSpacer(16.dp)
+
+ Caption13Up(
+ text = stringResource(R.string.widgets__widget__data),
+ color = Colors.White64,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ BlockToggleRow(
+ icon = R.drawable.ic_cube,
+ label = stringResource(R.string.widgets__blocks__field__block),
+ value = previewBlock.height,
+ isEnabled = prefs.showBlock,
+ onToggle = onToggleBlock,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_clock,
+ label = stringResource(R.string.widgets__blocks__field__time),
+ value = previewBlock.time,
+ isEnabled = prefs.showTime,
+ onToggle = onToggleTime,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_calendar,
+ label = stringResource(R.string.widgets__blocks__field__date),
+ value = previewBlock.date,
+ isEnabled = prefs.showDate,
+ onToggle = onToggleDate,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_transfer,
+ label = stringResource(R.string.widgets__blocks__field__transactions),
+ value = previewBlock.transactionCount,
+ isEnabled = prefs.showTransactions,
+ onToggle = onToggleTransactions,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_file_text,
+ label = stringResource(R.string.widgets__blocks__field__size),
+ value = previewBlock.size,
+ isEnabled = prefs.showSize,
+ onToggle = onToggleSize,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_coins,
+ label = stringResource(R.string.widgets__blocks__field__fees),
+ value = previewBlock.fees,
+ isEnabled = prefs.showFees,
+ onToggle = onToggleFees,
+ )
+ BlockToggleRow(
+ icon = R.drawable.ic_globe,
+ label = stringResource(R.string.widgets__widget__source),
+ value = previewBlock.source,
+ isEnabled = prefs.showSource,
+ onToggle = onToggleSource,
+ )
+ }
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth()
+ ) {
+ SecondaryButton(
+ text = stringResource(R.string.common__reset),
+ enabled = prefs != BlocksPreferences(),
+ fullWidth = false,
+ onClick = onReset,
+ modifier = Modifier.weight(1f)
+ )
+ PrimaryButton(
+ text = stringResource(R.string.common__save),
+ isLoading = state.isSaving,
+ enabled = !state.isSaving && prefs.run {
+ showBlock || showTime || showDate || showTransactions || showSize || showFees || showSource
+ },
+ fullWidth = false,
+ onClick = onSave,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
+
+@Composable
+private fun BlockToggleRow(
+ modifier: Modifier = Modifier,
+ @DrawableRes icon: Int,
+ label: String,
+ value: String,
+ isEnabled: Boolean,
+ onToggle: () -> Unit,
+) {
+ Column(modifier = modifier) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = null,
+ tint = Colors.Brand,
+ modifier = Modifier.size(20.dp)
+ )
+ BodyM(
+ text = label,
+ color = Colors.White80,
+ modifier = Modifier.weight(1f)
+ )
+ if (value.isNotEmpty()) {
+ BodySSB(
+ text = value,
+ color = Colors.White,
+ )
+ }
+ IconButton(onClick = onToggle) {
+ Icon(
+ painter = painterResource(R.drawable.ic_checkmark),
+ contentDescription = null,
+ tint = if (isEnabled) Colors.Brand else Colors.White50,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+ HorizontalDivider()
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/config/HeadlinesConfigContent.kt b/app/src/main/java/to/bitkit/appwidget/config/HeadlinesConfigContent.kt
new file mode 100644
index 0000000000..2138c97703
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/config/HeadlinesConfigContent.kt
@@ -0,0 +1,162 @@
+package to.bitkit.appwidget.config
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import to.bitkit.R
+import to.bitkit.models.widget.HeadlinePreferences
+import to.bitkit.ui.components.BodySSB
+import to.bitkit.ui.components.Caption13Up
+import to.bitkit.ui.components.PrimaryButton
+import to.bitkit.ui.components.SecondaryButton
+import to.bitkit.ui.components.Title
+import to.bitkit.ui.components.VerticalSpacer
+import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.scaffold.ScreenColumn
+import to.bitkit.ui.theme.Colors
+
+@Composable
+internal fun HeadlinesConfigContent(
+ state: AppWidgetConfigUiState,
+ onToggleSource: () -> Unit,
+ onToggleTime: () -> Unit,
+ onReset: () -> Unit,
+ onSave: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ val prefs = state.headlinePreferences
+ val previewArticle = state.previewArticle
+
+ ScreenColumn(
+ noBackground = true,
+ modifier = Modifier.background(Colors.Gray7)
+ ) {
+ AppTopBar(
+ titleText = stringResource(R.string.widgets__news__name),
+ onBackClick = onCancel,
+ )
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ VerticalSpacer(16.dp)
+
+ Caption13Up(
+ text = stringResource(R.string.widgets__widget__content),
+ color = Colors.White64,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ ToggleRow(
+ content = {
+ Title(
+ text = previewArticle.title,
+ modifier = Modifier.weight(1f)
+ )
+ },
+ isEnabled = true,
+ onToggle = {},
+ toggleEnabled = false,
+ )
+ HorizontalDivider()
+
+ ToggleRow(
+ content = {
+ BodySSB(
+ text = previewArticle.publisher,
+ color = Colors.Brand,
+ modifier = Modifier.weight(1f)
+ )
+ },
+ isEnabled = prefs.showSource,
+ onToggle = onToggleSource,
+ )
+ HorizontalDivider()
+
+ ToggleRow(
+ content = {
+ BodySSB(
+ text = previewArticle.timeAgo,
+ color = Colors.White64,
+ modifier = Modifier.weight(1f)
+ )
+ },
+ isEnabled = prefs.showTime,
+ onToggle = onToggleTime,
+ )
+ HorizontalDivider()
+ }
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth()
+ ) {
+ SecondaryButton(
+ text = stringResource(R.string.common__reset),
+ enabled = prefs != HeadlinePreferences(),
+ fullWidth = false,
+ onClick = onReset,
+ modifier = Modifier.weight(1f)
+ )
+ PrimaryButton(
+ text = stringResource(R.string.common__save),
+ isLoading = state.isSaving,
+ enabled = !state.isSaving,
+ fullWidth = false,
+ onClick = onSave,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
+
+@Composable
+private fun ToggleRow(
+ content: @Composable RowScope.() -> Unit,
+ isEnabled: Boolean,
+ onToggle: () -> Unit,
+ modifier: Modifier = Modifier,
+ toggleEnabled: Boolean = true,
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .padding(vertical = 8.dp)
+ .fillMaxWidth()
+ ) {
+ content()
+ IconButton(
+ onClick = onToggle,
+ enabled = toggleEnabled,
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_checkmark),
+ contentDescription = null,
+ tint = if (isEnabled) Colors.Brand else Colors.White50,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/config/PriceConfigContent.kt b/app/src/main/java/to/bitkit/appwidget/config/PriceConfigContent.kt
new file mode 100644
index 0000000000..9fe1fd458c
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/config/PriceConfigContent.kt
@@ -0,0 +1,150 @@
+package to.bitkit.appwidget.config
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import to.bitkit.R
+import to.bitkit.data.dto.price.GraphPeriod
+import to.bitkit.data.dto.price.TradingPair
+import to.bitkit.ext.label
+import to.bitkit.models.widget.PricePreferences
+import to.bitkit.ui.components.BodySSB
+import to.bitkit.ui.components.Caption13Up
+import to.bitkit.ui.components.PrimaryButton
+import to.bitkit.ui.components.SecondaryButton
+import to.bitkit.ui.components.VerticalSpacer
+import to.bitkit.ui.scaffold.AppTopBar
+import to.bitkit.ui.scaffold.ScreenColumn
+import to.bitkit.ui.theme.Colors
+
+@Composable
+internal fun PriceConfigContent(
+ state: AppWidgetConfigUiState,
+ onSelectPair: (TradingPair) -> Unit,
+ onSelectPeriod: (GraphPeriod) -> Unit,
+ onReset: () -> Unit,
+ onSave: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ val prefs = state.pricePreferences
+ val selectedPair = prefs.enabledPairs.firstOrNull() ?: TradingPair.BTC_USD
+
+ ScreenColumn(
+ noBackground = true,
+ modifier = Modifier.background(Colors.Gray7)
+ ) {
+ AppTopBar(
+ titleText = stringResource(R.string.widgets__price__name),
+ onBackClick = onCancel,
+ )
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ VerticalSpacer(16.dp)
+
+ Caption13Up(
+ text = stringResource(R.string.appwidget__price__currency),
+ color = Colors.White64,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ for (pair in TradingPair.entries) {
+ SelectableRow(
+ label = pair.displayName,
+ isSelected = pair == selectedPair,
+ onClick = { onSelectPair(pair) },
+ )
+ }
+
+ VerticalSpacer(16.dp)
+ Caption13Up(
+ text = stringResource(R.string.appwidget__price__timeframe),
+ color = Colors.White64,
+ modifier = Modifier.padding(vertical = 16.dp)
+ )
+
+ for (period in GraphPeriod.entries) {
+ SelectableRow(
+ label = period.label(),
+ isSelected = period == prefs.period,
+ onClick = { onSelectPeriod(period) },
+ )
+ }
+ }
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxWidth()
+ ) {
+ SecondaryButton(
+ text = stringResource(R.string.common__reset),
+ enabled = prefs != PricePreferences(),
+ fullWidth = false,
+ onClick = onReset,
+ modifier = Modifier.weight(1f)
+ )
+ PrimaryButton(
+ text = stringResource(R.string.common__save),
+ isLoading = state.isSaving,
+ enabled = !state.isSaving,
+ fullWidth = false,
+ onClick = onSave,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
+
+@Composable
+private fun SelectableRow(
+ label: String,
+ isSelected: Boolean,
+ onClick: () -> Unit,
+) {
+ Column {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = onClick)
+ .padding(vertical = 14.dp)
+ ) {
+ BodySSB(
+ text = label,
+ color = if (isSelected) Colors.White else Colors.White64,
+ modifier = Modifier.weight(1f)
+ )
+ if (isSelected) {
+ Icon(
+ painter = painterResource(R.drawable.ic_checkmark),
+ contentDescription = null,
+ tint = Colors.Brand,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+ HorizontalDivider()
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/model/AppWidgetPreferences.kt b/app/src/main/java/to/bitkit/appwidget/model/AppWidgetPreferences.kt
index 608ad3f976..1328eec788 100644
--- a/app/src/main/java/to/bitkit/appwidget/model/AppWidgetPreferences.kt
+++ b/app/src/main/java/to/bitkit/appwidget/model/AppWidgetPreferences.kt
@@ -3,6 +3,7 @@ package to.bitkit.appwidget.model
import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
import to.bitkit.data.dto.ArticleDTO
+import to.bitkit.data.dto.BlockDTO
import to.bitkit.data.dto.price.GraphPeriod
import to.bitkit.data.dto.price.PriceDTO
import to.bitkit.data.dto.price.TradingPair
@@ -10,6 +11,7 @@ import to.bitkit.data.dto.price.TradingPair
enum class AppWidgetType {
PRICE,
HEADLINES,
+ BLOCKS,
}
@Stable
@@ -19,6 +21,7 @@ data class AppWidgetEntry(
val type: AppWidgetType,
val pricePreferences: HomePricePreferences = HomePricePreferences(),
val headlinePreferences: HomeHeadlinePreferences = HomeHeadlinePreferences(),
+ val blocksPreferences: HomeBlocksPreferences = HomeBlocksPreferences(),
)
@Stable
@@ -35,6 +38,18 @@ data class HomeHeadlinePreferences(
val showSource: Boolean = true,
)
+@Stable
+@Serializable
+data class HomeBlocksPreferences(
+ val showBlock: Boolean = true,
+ val showTime: Boolean = true,
+ val showDate: Boolean = true,
+ val showTransactions: Boolean = true,
+ val showSize: Boolean = false,
+ val showFees: Boolean = false,
+ val showSource: Boolean = false,
+)
+
@Stable
@Serializable
data class AppWidgetData(
@@ -42,4 +57,5 @@ data class AppWidgetData(
val cachedPrices: Map = emptyMap(),
val cachedArticles: List = emptyList(),
val articleRotationTick: Int = 0,
+ val cachedBlock: BlockDTO? = null,
)
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceContent.kt b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceContent.kt
new file mode 100644
index 0000000000..7c9299e459
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceContent.kt
@@ -0,0 +1,187 @@
+package to.bitkit.appwidget.ui.blocks
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import androidx.glance.ColorFilter
+import androidx.glance.GlanceModifier
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.LocalContext
+import androidx.glance.LocalSize
+import androidx.glance.appwidget.action.actionStartActivity
+import androidx.glance.color.ColorProvider
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Column
+import androidx.glance.layout.Row
+import androidx.glance.layout.WidthModifier
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.fillMaxWidth
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+import androidx.glance.unit.Dimension
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
+import to.bitkit.R
+import to.bitkit.appwidget.config.AppWidgetConfigActivity
+import to.bitkit.appwidget.model.AppWidgetEntry
+import to.bitkit.appwidget.model.AppWidgetType
+import to.bitkit.appwidget.model.HomeBlocksPreferences
+import to.bitkit.appwidget.ui.components.BodyMSB
+import to.bitkit.appwidget.ui.components.BodySSB
+import to.bitkit.appwidget.ui.components.CaptionB
+import to.bitkit.appwidget.ui.components.GlanceLayoutDimens
+import to.bitkit.appwidget.ui.components.GlanceWidgetScaffold
+import to.bitkit.appwidget.ui.components.HorizontalSpacer
+import to.bitkit.appwidget.ui.components.VerticalSpacer
+import to.bitkit.appwidget.ui.theme.GlanceColors
+import to.bitkit.models.widget.BlockModel
+import to.bitkit.ui.theme.Colors
+
+private const val MAX_SMALL_ROWS = 4
+
+private data class BlockRow(
+ @DrawableRes val icon: Int,
+ val label: String,
+ val value: String,
+)
+
+@Suppress("RestrictedApi")
+@Composable
+fun BlocksGlanceContent(
+ entry: AppWidgetEntry,
+ block: BlockModel?,
+) {
+ val context = LocalContext.current
+ val configIntent = Intent(context, AppWidgetConfigActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, entry.appWidgetId)
+ putExtra(AppWidgetConfigActivity.EXTRA_WIDGET_TYPE, AppWidgetType.BLOCKS.name)
+ }
+
+ GlanceWidgetScaffold(onClick = actionStartActivity(configIntent)) {
+ if (block == null) {
+ CaptionB(text = context.getString(R.string.appwidget__loading))
+ return@GlanceWidgetScaffold
+ }
+
+ val rows = buildRows(context, entry.blocksPreferences, block)
+ if (rows.isEmpty()) {
+ CaptionB(text = context.getString(R.string.appwidget__loading))
+ return@GlanceWidgetScaffold
+ }
+
+ if (LocalSize.current.width >= GlanceLayoutDimens.WIDE_LAYOUT_MIN_WIDTH) {
+ WideContent(rows = rows)
+ } else {
+ CompactContent(rows = rows.take(MAX_SMALL_ROWS).toImmutableList())
+ }
+ }
+}
+
+@Suppress("RestrictedApi")
+@Composable
+private fun WideContent(rows: ImmutableList) {
+ Column(modifier = GlanceModifier.fillMaxSize()) {
+ rows.forEach { row ->
+ WideRow(row = row, modifier = GlanceModifier.padding(vertical = 6.dp))
+ }
+ }
+}
+
+@Suppress("RestrictedApi")
+@Composable
+private fun WideRow(row: BlockRow, modifier: GlanceModifier = GlanceModifier) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.fillMaxWidth()
+ ) {
+ Image(
+ provider = ImageProvider(row.icon),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(ColorProvider(day = Colors.Brand, night = Colors.Brand)),
+ modifier = GlanceModifier.size(20.dp)
+ )
+ HorizontalSpacer(8.dp)
+ BodyMSB(
+ text = row.label,
+ color = GlanceColors.textSecondary,
+ modifier = GlanceModifier.then(WidthModifier(Dimension.Expand))
+ )
+ BodyMSB(text = row.value)
+ }
+}
+
+@Suppress("RestrictedApi")
+@Composable
+private fun CompactContent(rows: ImmutableList) {
+ Column(modifier = GlanceModifier.fillMaxWidth()) {
+ rows.forEachIndexed { index, row ->
+ if (index > 0) VerticalSpacer(16.dp)
+ CompactRow(row = row)
+ }
+ }
+}
+
+@Suppress("RestrictedApi")
+@Composable
+private fun CompactRow(row: BlockRow) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = GlanceModifier.fillMaxWidth()
+ ) {
+ Image(
+ provider = ImageProvider(row.icon),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(ColorProvider(day = Colors.Brand, night = Colors.Brand)),
+ modifier = GlanceModifier.size(20.dp)
+ )
+ HorizontalSpacer(8.dp)
+ BodySSB(text = row.value)
+ }
+}
+
+private fun buildRows(
+ context: Context,
+ preferences: HomeBlocksPreferences,
+ block: BlockModel,
+): ImmutableList = listOfNotNull(
+ BlockRow(
+ icon = R.drawable.ic_cube,
+ label = context.getString(R.string.widgets__blocks__field__block),
+ value = block.height,
+ ).takeIf { preferences.showBlock && block.height.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_clock,
+ label = context.getString(R.string.widgets__blocks__field__time),
+ value = block.time,
+ ).takeIf { preferences.showTime && block.time.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_calendar,
+ label = context.getString(R.string.widgets__blocks__field__date),
+ value = block.date,
+ ).takeIf { preferences.showDate && block.date.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_transfer,
+ label = context.getString(R.string.widgets__blocks__field__transactions),
+ value = block.transactionCount,
+ ).takeIf { preferences.showTransactions && block.transactionCount.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_file_text,
+ label = context.getString(R.string.widgets__blocks__field__size),
+ value = block.size,
+ ).takeIf { preferences.showSize && block.size.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_coins,
+ label = context.getString(R.string.widgets__blocks__field__fees),
+ value = block.fees,
+ ).takeIf { preferences.showFees && block.fees.isNotEmpty() },
+ BlockRow(
+ icon = R.drawable.ic_globe,
+ label = context.getString(R.string.widgets__widget__source),
+ value = block.source,
+ ).takeIf { preferences.showSource && block.source.isNotEmpty() },
+).toImmutableList()
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceReceiver.kt b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceReceiver.kt
new file mode 100644
index 0000000000..02299e182d
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceReceiver.kt
@@ -0,0 +1,40 @@
+package to.bitkit.appwidget.ui.blocks
+
+import android.content.Context
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import dagger.hilt.android.EntryPointAccessors
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import to.bitkit.appwidget.AppWidgetEntryPoint
+import to.bitkit.appwidget.AppWidgetRefreshWorker
+
+class BlocksGlanceReceiver : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget = BlocksGlanceWidget()
+
+ override fun onEnabled(context: Context) {
+ super.onEnabled(context)
+ AppWidgetRefreshWorker.enqueue(context)
+ }
+
+ override fun onDeleted(context: Context, appWidgetIds: IntArray) {
+ super.onDeleted(context, appWidgetIds)
+ val pendingResult = goAsync()
+ val store = EntryPointAccessors
+ .fromApplication(context, AppWidgetEntryPoint::class.java)
+ .appWidgetPreferencesStore()
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ appWidgetIds.forEach { store.unregisterWidget(it) }
+ } finally {
+ pendingResult.finish()
+ }
+ }
+ }
+
+ override fun onDisabled(context: Context) {
+ super.onDisabled(context)
+ AppWidgetRefreshWorker.cancelIfNoWidgets(context)
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceWidget.kt b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceWidget.kt
new file mode 100644
index 0000000000..cc2442e058
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/ui/blocks/BlocksGlanceWidget.kt
@@ -0,0 +1,40 @@
+package to.bitkit.appwidget.ui.blocks
+
+import android.content.Context
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.glance.GlanceId
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetManager
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.provideContent
+import dagger.hilt.android.EntryPointAccessors
+import to.bitkit.appwidget.AppWidgetEntryPoint
+import to.bitkit.appwidget.model.AppWidgetData
+import to.bitkit.appwidget.model.AppWidgetEntry
+import to.bitkit.appwidget.model.AppWidgetType
+import to.bitkit.models.widget.toBlockModel
+
+class BlocksGlanceWidget : GlanceAppWidget() {
+
+ override val sizeMode = SizeMode.Exact
+
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+ val store = EntryPointAccessors
+ .fromApplication(context, AppWidgetEntryPoint::class.java)
+ .appWidgetPreferencesStore()
+ val appWidgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
+
+ provideContent {
+ val data by store.data.collectAsState(initial = AppWidgetData())
+ val entry = data.entries.find { it.appWidgetId == appWidgetId }
+ ?: AppWidgetEntry(appWidgetId = appWidgetId, type = AppWidgetType.BLOCKS)
+ val block = data.cachedBlock?.toBlockModel()
+
+ BlocksGlanceContent(
+ entry = entry,
+ block = block,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/components/GlanceWidgetScaffold.kt b/app/src/main/java/to/bitkit/appwidget/ui/components/GlanceWidgetScaffold.kt
index 37aed3364e..e1b8d8d741 100644
--- a/app/src/main/java/to/bitkit/appwidget/ui/components/GlanceWidgetScaffold.kt
+++ b/app/src/main/java/to/bitkit/appwidget/ui/components/GlanceWidgetScaffold.kt
@@ -1,12 +1,11 @@
package to.bitkit.appwidget.ui.components
-import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.ImageProvider
+import androidx.glance.action.Action
import androidx.glance.action.clickable
-import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.background
import androidx.glance.layout.Column
import androidx.glance.layout.fillMaxSize
@@ -15,7 +14,7 @@ import to.bitkit.R
@Composable
fun GlanceWidgetScaffold(
- onClick: Intent? = null,
+ onClick: Action? = null,
content: @Composable () -> Unit,
) {
val modifier = GlanceModifier
@@ -23,7 +22,7 @@ fun GlanceWidgetScaffold(
.background(ImageProvider(R.drawable.appwidget_background))
.padding(16.dp)
.let { mod ->
- if (onClick != null) mod.clickable(actionStartActivity(onClick)) else mod
+ if (onClick != null) mod.clickable(onClick) else mod
}
Column(modifier = modifier) {
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/headlines/HeadlinesGlanceContent.kt b/app/src/main/java/to/bitkit/appwidget/ui/headlines/HeadlinesGlanceContent.kt
index 0ef2e54c24..486353142c 100644
--- a/app/src/main/java/to/bitkit/appwidget/ui/headlines/HeadlinesGlanceContent.kt
+++ b/app/src/main/java/to/bitkit/appwidget/ui/headlines/HeadlinesGlanceContent.kt
@@ -7,6 +7,9 @@ import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.LocalContext
import androidx.glance.LocalSize
+import androidx.glance.action.actionParametersOf
+import androidx.glance.appwidget.action.actionRunCallback
+import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.HeightModifier
@@ -28,7 +31,6 @@ import to.bitkit.appwidget.ui.components.VerticalSpacer
import to.bitkit.appwidget.ui.theme.GlanceColors
import to.bitkit.appwidget.ui.theme.GlanceTextStyles
import to.bitkit.models.widget.ArticleModel
-import to.bitkit.models.widget.safeBrowserUri
import to.bitkit.ui.theme.Colors
@Suppress("RestrictedApi")
@@ -38,20 +40,20 @@ fun HeadlinesGlanceContent(
article: ArticleModel?,
) {
val context = LocalContext.current
- val articleUri = article?.safeBrowserUri()
- val tapIntent = if (articleUri != null) {
- Intent(Intent.ACTION_VIEW, articleUri).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
+ val tapAction = if (article != null && article.link.isNotEmpty()) {
+ actionRunCallback(
+ actionParametersOf(OpenUrlAction.linkKey to article.link)
+ )
} else {
- Intent(context, AppWidgetConfigActivity::class.java).apply {
+ val configIntent = Intent(context, AppWidgetConfigActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, entry.appWidgetId)
putExtra(AppWidgetConfigActivity.EXTRA_WIDGET_TYPE, AppWidgetType.HEADLINES.name)
}
+ actionStartActivity(configIntent)
}
- GlanceWidgetScaffold(onClick = tapIntent) {
+ GlanceWidgetScaffold(onClick = tapAction) {
if (article == null) {
CaptionB(text = context.getString(R.string.appwidget__loading))
return@GlanceWidgetScaffold
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/headlines/OpenUrlAction.kt b/app/src/main/java/to/bitkit/appwidget/ui/headlines/OpenUrlAction.kt
new file mode 100644
index 0000000000..9d8c23ded7
--- /dev/null
+++ b/app/src/main/java/to/bitkit/appwidget/ui/headlines/OpenUrlAction.kt
@@ -0,0 +1,31 @@
+package to.bitkit.appwidget.ui.headlines
+
+import android.content.Context
+import android.content.Intent
+import androidx.glance.GlanceId
+import androidx.glance.action.ActionParameters
+import androidx.glance.appwidget.action.ActionCallback
+import to.bitkit.models.widget.safeBrowserUri
+import to.bitkit.utils.Logger
+
+private const val TAG = "OpenUrlAction"
+
+class OpenUrlAction : ActionCallback {
+ companion object {
+ val linkKey = ActionParameters.Key("article_link")
+ }
+
+ override suspend fun onAction(
+ context: Context,
+ glanceId: GlanceId,
+ parameters: ActionParameters,
+ ) {
+ val link = parameters[linkKey] ?: return
+ val uri = safeBrowserUri(link) ?: return
+ val intent = Intent(Intent.ACTION_VIEW, uri).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ runCatching { context.startActivity(intent) }
+ .onFailure { Logger.error("Failed to open url '$link'", it, context = TAG) }
+ }
+}
diff --git a/app/src/main/java/to/bitkit/appwidget/ui/price/PriceGlanceContent.kt b/app/src/main/java/to/bitkit/appwidget/ui/price/PriceGlanceContent.kt
index f91ebad969..2bd5cec455 100644
--- a/app/src/main/java/to/bitkit/appwidget/ui/price/PriceGlanceContent.kt
+++ b/app/src/main/java/to/bitkit/appwidget/ui/price/PriceGlanceContent.kt
@@ -10,6 +10,7 @@ import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.LocalSize
+import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
@@ -51,7 +52,7 @@ fun PriceGlanceContent(
putExtra(AppWidgetConfigActivity.EXTRA_WIDGET_TYPE, AppWidgetType.PRICE.name)
}
- GlanceWidgetScaffold(onClick = configIntent) {
+ GlanceWidgetScaffold(onClick = actionStartActivity(configIntent)) {
if (widget == null) {
CaptionB(text = context.getString(R.string.appwidget__loading))
return@GlanceWidgetScaffold
diff --git a/app/src/main/java/to/bitkit/data/dto/BlockDTO.kt b/app/src/main/java/to/bitkit/data/dto/BlockDTO.kt
index ac297317e5..b273a5139f 100644
--- a/app/src/main/java/to/bitkit/data/dto/BlockDTO.kt
+++ b/app/src/main/java/to/bitkit/data/dto/BlockDTO.kt
@@ -13,4 +13,5 @@ data class BlockDTO(
val difficulty: String,
val merkleRoot: String,
val source: String,
+ val fees: String = "",
)
diff --git a/app/src/main/java/to/bitkit/data/dto/MempoolBlockInfo.kt b/app/src/main/java/to/bitkit/data/dto/MempoolBlockInfo.kt
index 2a236425b9..336dc4671f 100644
--- a/app/src/main/java/to/bitkit/data/dto/MempoolBlockInfo.kt
+++ b/app/src/main/java/to/bitkit/data/dto/MempoolBlockInfo.kt
@@ -15,5 +15,11 @@ data class MempoolBlockInfo(
val size: Long,
val weight: Long,
val difficulty: Double,
- @SerialName("merkle_root") val merkleRoot: String
+ @SerialName("merkle_root") val merkleRoot: String,
+ val extras: BlockExtras? = null,
+)
+
+@Serializable
+data class BlockExtras(
+ @SerialName("totalFees") val totalFees: Long? = null,
)
diff --git a/app/src/main/java/to/bitkit/data/widgets/BlocksService.kt b/app/src/main/java/to/bitkit/data/widgets/BlocksService.kt
index 4195b50aa3..666c29da2c 100644
--- a/app/src/main/java/to/bitkit/data/widgets/BlocksService.kt
+++ b/app/src/main/java/to/bitkit/data/widgets/BlocksService.kt
@@ -45,7 +45,7 @@ class BlocksService @Inject constructor(
}
private suspend fun getBlockInfo(hash: String): MempoolBlockInfo {
- val response: HttpResponse = client.get("${Env.mempoolBaseUrl}/block/$hash")
+ val response: HttpResponse = client.get("${Env.mempoolBaseUrl}/v1/block/$hash")
return when (response.status.isSuccess()) {
true -> {
val responseBody = runCatching { response.body() }.getOrElse {
@@ -75,6 +75,7 @@ class BlocksService @Inject constructor(
// Format other numbers
val formattedHeight = numberFormat.format(blockInfo.height)
val formattedTransactionCount = numberFormat.format(blockInfo.txCount)
+ val formattedFees = blockInfo.extras?.totalFees?.let { numberFormat.format(it).replace(',', ' ') }.orEmpty()
// Format timestamp to date and time
val timestamp = blockInfo.timestamp * 1000L // Convert to milliseconds
@@ -88,7 +89,8 @@ class BlocksService @Inject constructor(
weight = formattedWeight,
difficulty = difficulty,
merkleRoot = blockInfo.merkleRoot,
- source = Env.mempoolBaseUrl.replace("https://", "").replaceAfter("/", "").replace("/", "")
+ source = Env.mempoolBaseUrl.replace("https://", "").replaceAfter("/", "").replace("/", ""),
+ fees = formattedFees,
)
}
diff --git a/app/src/main/java/to/bitkit/models/widget/BlockModel.kt b/app/src/main/java/to/bitkit/models/widget/BlockModel.kt
index 5d260a9d17..7741ec1da1 100644
--- a/app/src/main/java/to/bitkit/models/widget/BlockModel.kt
+++ b/app/src/main/java/to/bitkit/models/widget/BlockModel.kt
@@ -15,6 +15,7 @@ data class BlockModel(
val transactionCount: String,
val size: String,
val source: String,
+ val fees: String,
)
fun BlockDTO.toBlockModel() = BlockModel(
@@ -23,5 +24,6 @@ fun BlockDTO.toBlockModel() = BlockModel(
date = this.timestamp.toDateUTC(),
transactionCount = this.transactionCount,
size = this.size,
- source = this.source
+ source = this.source,
+ fees = this.fees,
)
diff --git a/app/src/main/java/to/bitkit/models/widget/BlocksPreferences.kt b/app/src/main/java/to/bitkit/models/widget/BlocksPreferences.kt
index 2ae2d943e3..6d9a911de0 100644
--- a/app/src/main/java/to/bitkit/models/widget/BlocksPreferences.kt
+++ b/app/src/main/java/to/bitkit/models/widget/BlocksPreferences.kt
@@ -9,7 +9,8 @@ data class BlocksPreferences(
val showBlock: Boolean = true,
val showTime: Boolean = true,
val showDate: Boolean = true,
- val showTransactions: Boolean = false,
+ val showTransactions: Boolean = true,
val showSize: Boolean = false,
+ val showFees: Boolean = false,
val showSource: Boolean = false,
)
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
index 3993ebad7e..32a0930a3e 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
@@ -731,17 +731,18 @@ private fun Widgets(
WidgetType.BLOCK -> {
homeUiState.currentBlock?.run {
BlockCard(
- showWidgetTitle = homeUiState.showWidgetTitles,
showBlock = homeUiState.blocksPreferences.showBlock,
showTime = homeUiState.blocksPreferences.showTime,
showDate = homeUiState.blocksPreferences.showDate,
showTransactions = homeUiState.blocksPreferences.showTransactions,
showSize = homeUiState.blocksPreferences.showSize,
+ showFees = homeUiState.blocksPreferences.showFees,
showSource = homeUiState.blocksPreferences.showSource,
time = time,
date = date,
transactions = transactionCount,
size = size,
+ fees = fees,
source = source,
block = height,
modifier = Modifier
@@ -978,6 +979,7 @@ private val previewBlock = BlockModel(
transactionCount = "2,175",
size = "1,606kB",
source = "mempool.io",
+ fees = "25 059 357",
)
private val previewArticle = ArticleModel(
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlockCard.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlockCard.kt
index e5c8235146..aa94c62b4c 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlockCard.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlockCard.kt
@@ -1,50 +1,51 @@
package to.bitkit.ui.screens.widgets.blocks
+import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import to.bitkit.R
+import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyMSB
import to.bitkit.ui.components.BodySSB
-import to.bitkit.ui.components.CaptionB
+import to.bitkit.ui.screens.widgets.components.WidgetCardDimens
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
+@Suppress("CyclomaticComplexMethod")
@Composable
fun BlockCard(
modifier: Modifier = Modifier,
- showWidgetTitle: Boolean,
showBlock: Boolean,
showTime: Boolean,
showDate: Boolean,
showTransactions: Boolean,
showSize: Boolean,
+ showFees: Boolean,
showSource: Boolean,
block: String,
time: String,
date: String,
transactions: String,
size: String,
+ fees: String,
source: String,
) {
Box(
@@ -53,224 +54,286 @@ fun BlockCard(
.background(Colors.White10)
) {
Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
- .padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(8.dp)
+ .padding(16.dp)
) {
- if (showWidgetTitle) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .padding(bottom = 8.dp)
- .testTag("block_card_widget_title_row")
- ) {
- Icon(
- painter = painterResource(R.drawable.widget_cube),
- contentDescription = null,
- modifier = Modifier
- .size(32.dp)
- .testTag("block_card_widget_title_icon"),
- tint = Color.Unspecified
- )
- Spacer(modifier = Modifier.width(16.dp))
- BodyMSB(
- text = stringResource(R.string.widgets__blocks__name),
- modifier = Modifier.testTag("block_card_widget_title_text")
- )
- }
- }
-
if (showBlock && block.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("block_card_block_row"),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- BodySSB(
- text = "Block",
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_block_label")
- )
-
- BodyMSB(
- text = block,
- color = Colors.White,
- modifier = Modifier.testTag("block_card_block_text")
- )
- }
+ WidgetDataRow(
+ icon = R.drawable.ic_cube,
+ label = stringResource(R.string.widgets__blocks__field__block),
+ value = block,
+ testTagPrefix = "block",
+ )
}
-
if (showTime && time.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("block_card_time_row"),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- BodySSB(
- text = "Time",
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_time_label")
- )
-
- BodyMSB(
- text = time,
- color = Colors.White,
- modifier = Modifier.testTag("block_card_time_text")
- )
- }
+ WidgetDataRow(
+ icon = R.drawable.ic_clock,
+ label = stringResource(R.string.widgets__blocks__field__time),
+ value = time,
+ testTagPrefix = "time",
+ )
}
-
if (showDate && date.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("block_card_date_row"),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- BodySSB(
- text = "Date",
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_date_label")
- )
-
- BodyMSB(
- text = date,
- color = Colors.White,
- modifier = Modifier.testTag("block_card_date_text")
- )
- }
+ WidgetDataRow(
+ icon = R.drawable.ic_calendar,
+ label = stringResource(R.string.widgets__blocks__field__date),
+ value = date,
+ testTagPrefix = "date",
+ )
}
-
if (showTransactions && transactions.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("block_card_transactions_row"),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- BodySSB(
- text = "Transactions",
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_transactions_label")
- )
-
- BodyMSB(
- text = transactions,
- color = Colors.White,
- modifier = Modifier.testTag("block_card_transactions_text")
- )
- }
+ WidgetDataRow(
+ icon = R.drawable.ic_transfer,
+ label = stringResource(R.string.widgets__blocks__field__transactions),
+ value = transactions,
+ testTagPrefix = "transactions",
+ )
}
-
if (showSize && size.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("block_card_size_row"),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- BodySSB(
- text = "Size",
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_size_label")
- )
-
- BodyMSB(
- text = size,
- color = Colors.White,
- modifier = Modifier.testTag("block_card_size_text")
- )
- }
+ WidgetDataRow(
+ icon = R.drawable.ic_file_text,
+ label = stringResource(R.string.widgets__blocks__field__size),
+ value = size,
+ testTagPrefix = "size",
+ )
+ }
+ if (showFees && fees.isNotEmpty()) {
+ WidgetDataRow(
+ icon = R.drawable.ic_coins,
+ label = stringResource(R.string.widgets__blocks__field__fees),
+ value = fees,
+ testTagPrefix = "fees",
+ )
}
-
if (showSource && source.isNotEmpty()) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp)
- .testTag("block_card_source_row"),
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- CaptionB(
- text = stringResource(R.string.widgets__widget__source),
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_source_label")
- )
+ WidgetDataRow(
+ icon = R.drawable.ic_globe,
+ label = stringResource(R.string.widgets__widget__source),
+ value = source,
+ testTagPrefix = "source",
+ )
+ }
+ }
+ }
+}
- CaptionB(
- text = source,
- color = Colors.White64,
- modifier = Modifier.testTag("block_card_source_text")
- )
- }
+@Suppress("CyclomaticComplexMethod")
+@Composable
+fun BlockCardSmall(
+ modifier: Modifier = Modifier,
+ showBlock: Boolean,
+ showTime: Boolean,
+ showDate: Boolean,
+ showTransactions: Boolean,
+ showSize: Boolean,
+ showFees: Boolean,
+ showSource: Boolean,
+ block: String,
+ time: String,
+ date: String,
+ transactions: String,
+ size: String,
+ fees: String,
+ source: String,
+) {
+ val rows = listOfNotNull(
+ SmallRowData(R.drawable.ic_cube, block, "block").takeIf { showBlock && block.isNotEmpty() },
+ SmallRowData(R.drawable.ic_clock, time, "time").takeIf { showTime && time.isNotEmpty() },
+ SmallRowData(R.drawable.ic_calendar, date, "date").takeIf { showDate && date.isNotEmpty() },
+ SmallRowData(R.drawable.ic_transfer, transactions, "transactions")
+ .takeIf { showTransactions && transactions.isNotEmpty() },
+ SmallRowData(R.drawable.ic_file_text, size, "size").takeIf { showSize && size.isNotEmpty() },
+ SmallRowData(R.drawable.ic_coins, fees, "fees").takeIf { showFees && fees.isNotEmpty() },
+ SmallRowData(R.drawable.ic_globe, source, "source").takeIf { showSource && source.isNotEmpty() },
+ ).take(MAX_SMALL_ROWS)
+
+ Box(
+ modifier = modifier
+ .size(WidgetCardDimens.COMPACT_CARD_SIZE)
+ .clip(shape = MaterialTheme.shapes.medium)
+ .background(Colors.White10)
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ rows.forEach { row ->
+ SmallDataRow(
+ icon = row.icon,
+ value = row.value,
+ testTagPrefix = row.testTagPrefix,
+ )
}
}
}
}
+private const val MAX_SMALL_ROWS = 4
+
+private data class SmallRowData(
+ @DrawableRes val icon: Int,
+ val value: String,
+ val testTagPrefix: String,
+)
+
+@Composable
+private fun WidgetDataRow(
+ modifier: Modifier = Modifier,
+ @DrawableRes icon: Int,
+ label: String,
+ value: String,
+ testTagPrefix: String,
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .fillMaxWidth()
+ .testTag("${testTagPrefix}_row")
+ ) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = null,
+ tint = Colors.Brand,
+ modifier = Modifier
+ .size(20.dp)
+ .testTag("${testTagPrefix}_icon")
+ )
+ BodyM(
+ text = label,
+ color = Colors.White80,
+ modifier = Modifier
+ .weight(1f)
+ .testTag("${testTagPrefix}_label")
+ )
+ BodyMSB(
+ text = value,
+ color = Colors.White,
+ modifier = Modifier.testTag("${testTagPrefix}_text")
+ )
+ }
+}
+
+@Composable
+private fun SmallDataRow(
+ modifier: Modifier = Modifier,
+ @DrawableRes icon: Int,
+ value: String,
+ testTagPrefix: String,
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ .fillMaxWidth()
+ .testTag("${testTagPrefix}_row")
+ ) {
+ Icon(
+ painter = painterResource(icon),
+ contentDescription = null,
+ tint = Colors.Brand,
+ modifier = Modifier
+ .size(20.dp)
+ .testTag("${testTagPrefix}_icon")
+ )
+ BodySSB(
+ text = value,
+ color = Colors.White,
+ modifier = Modifier.testTag("${testTagPrefix}_text")
+ )
+ }
+}
+
@Preview(showBackground = true)
@Composable
-private fun FullBlockCardPreview() {
+private fun PreviewLargeAll() {
AppThemeSurface {
Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxSize()
- .padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(8.dp)
+ .padding(16.dp)
) {
BlockCard(
- showWidgetTitle = true,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
showSize = true,
+ showFees = true,
showSource = true,
block = "761,405",
time = "01:31:42 UTC",
date = "11/2/2022",
transactions = "2,175",
size = "1,606Kb",
+ fees = "25 059 357",
source = "mempool.io",
+ modifier = Modifier.fillMaxWidth()
)
+ }
+ }
+}
+@Preview(showBackground = true)
+@Composable
+private fun PreviewLargeDefault() {
+ AppThemeSurface {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
BlockCard(
- showWidgetTitle = false,
showBlock = true,
showTime = true,
showDate = true,
showTransactions = true,
- showSize = true,
+ showSize = false,
+ showFees = false,
showSource = false,
block = "761,405",
time = "01:31:42 UTC",
date = "11/2/2022",
transactions = "2,175",
- size = "1,606Kb",
- source = "mempool.io", // Source text is still provided but won't be shown
+ size = "",
+ fees = "",
+ source = "",
+ modifier = Modifier.fillMaxWidth()
)
+ }
+ }
+}
- BlockCard(
- showWidgetTitle = true,
+@Preview(showBackground = true)
+@Composable
+private fun PreviewSmall() {
+ AppThemeSurface {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ BlockCardSmall(
showBlock = true,
- showTime = false,
- showDate = false,
- showTransactions = false,
+ showTime = true,
+ showDate = true,
+ showTransactions = true,
showSize = false,
+ showFees = false,
showSource = false,
block = "761,405",
- time = "",
- date = "",
- transactions = "",
+ time = "01:31:42 UTC",
+ date = "11/2/2022",
+ transactions = "2,175",
size = "",
+ fees = "",
source = "",
)
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreen.kt
index 1ebb76e95c..cbf57f1d88 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksEditScreen.kt
@@ -1,15 +1,13 @@
package to.bitkit.ui.screens.widgets.blocks
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-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.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -26,12 +24,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.models.widget.BlockModel
import to.bitkit.models.widget.BlocksPreferences
-import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodySSB
+import to.bitkit.ui.components.Caption13Up
+import to.bitkit.ui.components.FillHeight
import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.SecondaryButton
+import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.scaffold.AppTopBar
-import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
@@ -51,10 +50,11 @@ fun BlocksEditScreen(
date = "",
transactionCount = "",
size = "",
- source = ""
+ source = "",
+ fees = "",
)
- BlocksEditContent(
+ Content(
onBack = onBack,
blocksPreferences = customPreference,
block = currentBlock ?: blockPlaceholder,
@@ -63,6 +63,7 @@ fun BlocksEditScreen(
onClickShowDate = { blocksViewModel.toggleShowDate() },
onClickShowTransactions = { blocksViewModel.toggleShowTransactions() },
onClickShowSize = { blocksViewModel.toggleShowSize() },
+ onClickShowFees = { blocksViewModel.toggleShowFees() },
onClickShowSource = { blocksViewModel.toggleShowSource() },
onClickReset = { blocksViewModel.resetCustomPreferences() },
onClickPreview = navigatePreview,
@@ -70,13 +71,14 @@ fun BlocksEditScreen(
}
@Composable
-fun BlocksEditContent(
+private fun Content(
onBack: () -> Unit,
onClickShowBlock: () -> Unit,
onClickShowTime: () -> Unit,
onClickShowDate: () -> Unit,
onClickShowTransactions: () -> Unit,
onClickShowSize: () -> Unit,
+ onClickShowFees: () -> Unit,
onClickShowSource: () -> Unit,
onClickReset: () -> Unit,
onClickPreview: () -> Unit,
@@ -84,123 +86,132 @@ fun BlocksEditContent(
block: BlockModel,
) {
ScreenColumn(
- modifier = Modifier.testTag("blocks_edit_screen")
+ noBackground = true,
+ modifier = Modifier
+ .background(Colors.Gray7)
+ .testTag("blocks_edit_screen")
) {
AppTopBar(
- titleText = stringResource(R.string.widgets__widget__edit),
+ titleText = stringResource(R.string.widgets__blocks__name),
onBackClick = onBack,
- actions = { DrawerNavIcon() },
)
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
- .weight(1f)
- .verticalScroll(rememberScrollState())
.testTag("WidgetEditScrollView")
) {
- Spacer(modifier = Modifier.height(26.dp))
+ VerticalSpacer(16.dp)
- BodyM(
- text = stringResource(R.string.widgets__widget__edit_description).replace(
- "{name}",
- stringResource(R.string.widgets__blocks__name)
- ),
+ Caption13Up(
+ text = stringResource(R.string.widgets__widget__data),
color = Colors.White64,
- modifier = Modifier.testTag("edit_description")
+ modifier = Modifier
+ .padding(bottom = 16.dp)
+ .testTag("data_section_header")
)
- Spacer(modifier = Modifier.height(32.dp))
-
- // Block number toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_cube,
label = stringResource(R.string.widgets__blocks__field__block),
value = block.height,
isEnabled = blocksPreferences.showBlock,
onClick = onClickShowBlock,
- testTagPrefix = "block"
+ testTagPrefix = "block",
)
- // Time toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_clock,
label = stringResource(R.string.widgets__blocks__field__time),
value = block.time,
isEnabled = blocksPreferences.showTime,
onClick = onClickShowTime,
- testTagPrefix = "time"
+ testTagPrefix = "time",
)
- // Date toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_calendar,
label = stringResource(R.string.widgets__blocks__field__date),
value = block.date,
isEnabled = blocksPreferences.showDate,
onClick = onClickShowDate,
- testTagPrefix = "date"
+ testTagPrefix = "date",
)
- // Transactions toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_transfer,
label = stringResource(R.string.widgets__blocks__field__transactions),
value = block.transactionCount,
isEnabled = blocksPreferences.showTransactions,
onClick = onClickShowTransactions,
- testTagPrefix = "transactions"
+ testTagPrefix = "transactions",
)
- // Size toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_file_text,
label = stringResource(R.string.widgets__blocks__field__size),
value = block.size,
isEnabled = blocksPreferences.showSize,
onClick = onClickShowSize,
- testTagPrefix = "size"
+ testTagPrefix = "size",
)
- // Source toggle
BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_coins,
+ label = stringResource(R.string.widgets__blocks__field__fees),
+ value = block.fees,
+ isEnabled = blocksPreferences.showFees,
+ onClick = onClickShowFees,
+ testTagPrefix = "fees",
+ )
+
+ BlockEditOptionRow(
+ leadingIcon = R.drawable.ic_globe,
label = stringResource(R.string.widgets__widget__source),
value = block.source,
isEnabled = blocksPreferences.showSource,
onClick = onClickShowSource,
- testTagPrefix = "source"
+ testTagPrefix = "source",
)
- }
- Row(
- modifier = Modifier
- .padding(vertical = 21.dp, horizontal = 16.dp)
- .fillMaxWidth()
- .testTag("buttons_row"),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- SecondaryButton(
- text = stringResource(R.string.common__reset),
- modifier = Modifier
- .weight(1f)
- .testTag("WidgetEditReset"),
- enabled = blocksPreferences != BlocksPreferences(),
- fullWidth = false,
- onClick = onClickReset
- )
+ FillHeight()
- PrimaryButton(
- text = stringResource(R.string.common__preview),
- enabled = blocksPreferences.run {
- showBlock || showTime || showDate || showTransactions || showSize || showSource
- },
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
- .weight(1f)
- .testTag("WidgetEditPreview"),
- fullWidth = false,
- onClick = onClickPreview
- )
+ .padding(vertical = 21.dp)
+ .fillMaxWidth()
+ .testTag("buttons_row")
+ ) {
+ SecondaryButton(
+ text = stringResource(R.string.common__reset),
+ enabled = blocksPreferences != BlocksPreferences(),
+ fullWidth = false,
+ onClick = onClickReset,
+ modifier = Modifier
+ .weight(1f)
+ .testTag("WidgetEditReset")
+ )
+
+ PrimaryButton(
+ text = stringResource(R.string.common__preview),
+ enabled = blocksPreferences.run {
+ showBlock || showTime || showDate || showTransactions || showSize || showFees || showSource
+ },
+ fullWidth = false,
+ onClick = onClickPreview,
+ modifier = Modifier
+ .weight(1f)
+ .testTag("WidgetEditPreview")
+ )
+ }
}
}
}
@Composable
private fun BlockEditOptionRow(
+ @DrawableRes leadingIcon: Int,
label: String,
value: String,
isEnabled: Boolean,
@@ -212,13 +223,21 @@ private fun BlockEditOptionRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
- .padding(vertical = 21.dp)
.fillMaxWidth()
.testTag("${testTagPrefix}_setting_row")
) {
+ Icon(
+ painter = painterResource(leadingIcon),
+ contentDescription = null,
+ tint = Colors.Brand,
+ modifier = Modifier
+ .size(20.dp)
+ .testTag("${testTagPrefix}_leading_icon")
+ )
+
BodySSB(
text = label,
- color = Colors.White64,
+ color = Colors.White80,
modifier = Modifier
.weight(1f)
.testTag("${testTagPrefix}_label")
@@ -242,7 +261,7 @@ private fun BlockEditOptionRow(
tint = if (isEnabled) Colors.Brand else Colors.White50,
modifier = Modifier
.size(32.dp)
- .testTag("${testTagPrefix}_toggle_icon"),
+ .testTag("${testTagPrefix}_toggle_icon")
)
}
}
@@ -257,13 +276,14 @@ private fun BlockEditOptionRow(
@Composable
private fun Preview() {
AppThemeSurface {
- BlocksEditContent(
+ Content(
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -274,7 +294,8 @@ private fun Preview() {
date = "01/2/2022",
transactionCount = "2,175",
size = "1,606kB",
- source = "mempool.io"
+ source = "mempool.io",
+ fees = "25 059 357",
),
)
}
@@ -284,13 +305,14 @@ private fun Preview() {
@Composable
private fun PreviewWithSomeOptionsEnabled() {
AppThemeSurface {
- BlocksEditContent(
+ Content(
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -300,7 +322,8 @@ private fun PreviewWithSomeOptionsEnabled() {
showDate = false,
showTransactions = true,
showSize = false,
- showSource = true
+ showFees = false,
+ showSource = true,
),
block = BlockModel(
height = "",
@@ -308,7 +331,8 @@ private fun PreviewWithSomeOptionsEnabled() {
date = "",
transactionCount = "",
size = "",
- source = ""
+ source = "",
+ fees = "",
),
)
}
@@ -318,13 +342,14 @@ private fun PreviewWithSomeOptionsEnabled() {
@Composable
private fun PreviewWithAllDisabled() {
AppThemeSurface {
- BlocksEditContent(
+ Content(
onBack = {},
onClickShowBlock = {},
onClickShowTime = {},
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
+ onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
@@ -334,7 +359,8 @@ private fun PreviewWithAllDisabled() {
showDate = false,
showTransactions = false,
showSize = false,
- showSource = false
+ showFees = false,
+ showSource = false,
),
block = BlockModel(
height = "",
@@ -342,7 +368,8 @@ private fun PreviewWithAllDisabled() {
date = "",
transactionCount = "",
size = "",
- source = ""
+ source = "",
+ fees = "",
),
)
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreen.kt
index 7fb89a6aca..8f6a30b396 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksPreviewScreen.kt
@@ -1,42 +1,33 @@
package to.bitkit.ui.screens.widgets.blocks
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-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.layout.size
import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
-import to.bitkit.ext.spaceToNewline
import to.bitkit.models.widget.BlockModel
import to.bitkit.models.widget.BlocksPreferences
import to.bitkit.ui.components.BodyM
-import to.bitkit.ui.components.Headline
import to.bitkit.ui.components.PrimaryButton
import to.bitkit.ui.components.SecondaryButton
-import to.bitkit.ui.components.Text13Up
+import to.bitkit.ui.components.VerticalSpacer
import to.bitkit.ui.components.settings.SettingsButtonRow
import to.bitkit.ui.components.settings.SettingsButtonValue
import to.bitkit.ui.scaffold.AppTopBar
-import to.bitkit.ui.scaffold.DrawerNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
+import to.bitkit.ui.screens.widgets.components.WidgetSizeCarousel
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
@@ -47,7 +38,6 @@ fun BlocksPreviewScreen(
onBack: () -> Unit,
navigateEditWidget: () -> Unit,
) {
- val showWidgetTitles by blocksViewModel.showWidgetTitles.collectAsStateWithLifecycle()
val customBlocksPreferences by blocksViewModel.customPreferences.collectAsStateWithLifecycle()
val currentBlock by blocksViewModel.currentBlock.collectAsStateWithLifecycle()
val isBlocksWidgetEnabled by blocksViewModel.isBlocksWidgetEnabled.collectAsStateWithLifecycle()
@@ -56,11 +46,10 @@ fun BlocksPreviewScreen(
blocksViewModel.refreshOnDisplay()
}
- BlocksPreviewContent(
+ Content(
onBack = onBack,
isBlocksWidgetEnabled = isBlocksWidgetEnabled,
blocksPreferences = customBlocksPreferences,
- showWidgetTitles = showWidgetTitles,
block = currentBlock,
onClickEdit = navigateEditWidget,
onClickDelete = {
@@ -75,136 +64,138 @@ fun BlocksPreviewScreen(
}
@Composable
-fun BlocksPreviewContent(
+private fun Content(
onBack: () -> Unit,
onClickEdit: () -> Unit,
onClickDelete: () -> Unit,
onClickSave: () -> Unit,
- showWidgetTitles: Boolean,
isBlocksWidgetEnabled: Boolean,
blocksPreferences: BlocksPreferences,
block: BlockModel?,
) {
ScreenColumn(
- modifier = Modifier.testTag("blocks_preview_screen")
+ noBackground = true,
+ modifier = Modifier
+ .background(Colors.Gray7)
+ .testTag("blocks_preview_screen")
) {
AppTopBar(
- titleText = stringResource(R.string.widgets__widget__nav_title),
+ titleText = stringResource(R.string.widgets__blocks__name),
onBackClick = onBack,
- actions = { DrawerNavIcon() },
)
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
- .testTag("main_content")
+ .weight(1f)
) {
- Spacer(modifier = Modifier.height(26.dp))
-
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .testTag("header_row"),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- Headline(
- text = AnnotatedString(stringResource(R.string.widgets__blocks__name).spaceToNewline()),
- modifier = Modifier.testTag("widget_title"),
- )
- Icon(
- painter = painterResource(R.drawable.widget_cube),
- contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier
- .size(64.dp)
- .testTag("widget_icon")
- )
- }
+ VerticalSpacer(16.dp)
BodyM(
text = stringResource(R.string.widgets__blocks__description),
color = Colors.White64,
- modifier = Modifier
- .padding(vertical = 16.dp)
- .testTag("widget_description")
+ modifier = Modifier.testTag("widget_description")
)
+ VerticalSpacer(16.dp)
+
HorizontalDivider(
modifier = Modifier.testTag("divider")
)
SettingsButtonRow(
- title = stringResource(R.string.widgets__widget__edit),
+ title = stringResource(R.string.widgets__widget__settings),
value = SettingsButtonValue.StringValue(
if (blocksPreferences == BlocksPreferences()) {
stringResource(R.string.widgets__widget__edit_default)
} else {
stringResource(R.string.widgets__widget__edit_custom)
- }
+ },
),
onClick = onClickEdit,
modifier = Modifier.testTag("WidgetEdit")
)
- Spacer(modifier = Modifier.weight(1f))
-
- Text13Up(
- stringResource(R.string.common__preview),
- color = Colors.White64,
- modifier = Modifier
- .padding(vertical = 16.dp)
- .testTag("preview_label")
- )
-
block?.let {
- BlockCard(
+ WidgetSizeCarousel(
+ smallContent = {
+ BlockCardSmall(
+ showBlock = blocksPreferences.showBlock,
+ showTime = blocksPreferences.showTime,
+ showDate = blocksPreferences.showDate,
+ showTransactions = blocksPreferences.showTransactions,
+ showSize = blocksPreferences.showSize,
+ showFees = blocksPreferences.showFees,
+ showSource = blocksPreferences.showSource,
+ block = it.height,
+ time = it.time,
+ date = it.date,
+ transactions = it.transactionCount,
+ size = it.size,
+ fees = it.fees,
+ source = it.source,
+ modifier = Modifier.testTag("block_card_small")
+ )
+ },
+ wideContent = {
+ BlockCard(
+ showBlock = blocksPreferences.showBlock,
+ showTime = blocksPreferences.showTime,
+ showDate = blocksPreferences.showDate,
+ showTransactions = blocksPreferences.showTransactions,
+ showSize = blocksPreferences.showSize,
+ showFees = blocksPreferences.showFees,
+ showSource = blocksPreferences.showSource,
+ block = it.height,
+ time = it.time,
+ date = it.date,
+ transactions = it.transactionCount,
+ size = it.size,
+ fees = it.fees,
+ source = it.source,
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag("block_card_wide")
+ )
+ },
modifier = Modifier
.fillMaxWidth()
- .testTag("block_card"),
- showWidgetTitle = showWidgetTitles,
- showBlock = blocksPreferences.showBlock,
- showTime = blocksPreferences.showTime,
- showDate = blocksPreferences.showDate,
- showTransactions = blocksPreferences.showTransactions,
- showSize = blocksPreferences.showSize,
- showSource = blocksPreferences.showSource,
- block = block.height,
- time = block.time,
- date = block.date,
- transactions = block.transactionCount,
- size = block.size,
- source = block.source,
+ .testTag("blocks_preview_carousel")
)
}
+ }
- Row(
- modifier = Modifier
- .padding(vertical = 21.dp)
- .fillMaxWidth()
- .testTag("buttons_row"),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- if (isBlocksWidgetEnabled) {
- SecondaryButton(
- text = stringResource(R.string.common__delete),
- modifier = Modifier
- .weight(1f)
- .testTag("WidgetDelete"),
- fullWidth = false,
- onClick = onClickDelete
- )
- }
-
- PrimaryButton(
- text = stringResource(R.string.common__save),
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier
+ .padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 16.dp,
+ top = 22.dp,
+ )
+ .fillMaxWidth()
+ .testTag("buttons_row")
+ ) {
+ if (isBlocksWidgetEnabled) {
+ SecondaryButton(
+ text = stringResource(R.string.common__delete),
+ fullWidth = false,
+ onClick = onClickDelete,
modifier = Modifier
.weight(1f)
- .testTag("WidgetSave"),
- fullWidth = false,
- onClick = onClickSave
+ .testTag("WidgetDelete")
)
}
+
+ PrimaryButton(
+ text = stringResource(R.string.widgets__widget__save),
+ fullWidth = false,
+ onClick = onClickSave,
+ modifier = Modifier
+ .weight(1f)
+ .testTag("WidgetSave")
+ )
}
}
}
@@ -213,9 +204,8 @@ fun BlocksPreviewContent(
@Composable
private fun Preview() {
AppThemeSurface {
- BlocksPreviewContent(
+ Content(
onBack = {},
- showWidgetTitles = true,
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
@@ -226,9 +216,10 @@ private fun Preview() {
date = "2023-01-01",
transactionCount = "2,175",
size = "1,606kB",
- source = "mempool.space"
+ source = "mempool.space",
+ fees = "25 059 357",
),
- isBlocksWidgetEnabled = false
+ isBlocksWidgetEnabled = false,
)
}
}
@@ -237,9 +228,8 @@ private fun Preview() {
@Composable
private fun Preview2() {
AppThemeSurface {
- BlocksPreviewContent(
+ Content(
onBack = {},
- showWidgetTitles = false,
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
@@ -257,9 +247,10 @@ private fun Preview2() {
date = "2023-01-01",
transactionCount = "2,175",
size = "1,606kB",
- source = "mempool.space"
+ source = "mempool.space",
+ fees = "25 059 357",
),
- isBlocksWidgetEnabled = true
+ isBlocksWidgetEnabled = true,
)
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksViewModel.kt
index 3f3c0b93ac..70e7415f35 100644
--- a/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksViewModel.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/widgets/blocks/BlocksViewModel.kt
@@ -19,6 +19,7 @@ import to.bitkit.repositories.WidgetsRepo
import javax.inject.Inject
@HiltViewModel
+@Suppress("TooManyFunctions")
class BlocksViewModel @Inject constructor(
private val widgetsRepo: WidgetsRepo
) : ViewModel() {
@@ -99,6 +100,12 @@ class BlocksViewModel @Inject constructor(
}
}
+ fun toggleShowFees() {
+ _customPreferences.update { preferences ->
+ preferences.copy(showFees = !preferences.showFees)
+ }
+ }
+
fun toggleShowSource() {
_customPreferences.update { preferences ->
preferences.copy(showSource = !preferences.showSource)
diff --git a/app/src/main/res/drawable/ic_cube.xml b/app/src/main/res/drawable/ic_cube.xml
new file mode 100644
index 0000000000..4797a6cabd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cube.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/appwidget_preview_blocks.xml b/app/src/main/res/layout/appwidget_preview_blocks.xml
new file mode 100644
index 0000000000..ef5c0ebc74
--- /dev/null
+++ b/app/src/main/res/layout/appwidget_preview_blocks.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ffa085d374..3a1213d84f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1140,6 +1140,7 @@
Examine various statistics on newly mined Bitcoin Blocks.
Block
Date
+ Fees
Size
Time
Transactions
@@ -1174,6 +1175,7 @@
Bitcoin Weather
Next block inclusion
CONTENT
+ DATA
Widget Feed
Custom
Default
diff --git a/app/src/main/res/xml/appwidget_info_blocks.xml b/app/src/main/res/xml/appwidget_info_blocks.xml
new file mode 100644
index 0000000000..6264f6165f
--- /dev/null
+++ b/app/src/main/res/xml/appwidget_info_blocks.xml
@@ -0,0 +1,17 @@
+
+
diff --git a/changelog.d/next/922.added.md b/changelog.d/next/922.added.md
new file mode 100644
index 0000000000..c30858c87e
--- /dev/null
+++ b/changelog.d/next/922.added.md
@@ -0,0 +1 @@
+Bitcoin Blocks home screen widget with v61 wide and compact layouts, including redesigned in-app preview and edit screens