Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7abfbae
feat: migrate BlocksEditScreen.kt to v61 design
jvsena42 Apr 29, 2026
f2a7f36
feat: migrate BlockCard.kt to v61 design
jvsena42 Apr 29, 2026
51d16b2
feat: migrate BlocksPreviewScreen.kt to v61 design
jvsena42 Apr 29, 2026
bfd8006
Merge branch 'feat/headlines-v61' into feat/blocks-v61
jvsena42 Apr 30, 2026
e6da639
Merge branch 'feat/headlines-v61' into feat/blocks-v61
jvsena42 Apr 30, 2026
2dca86c
feat: limit displayed fields
jvsena42 Apr 30, 2026
ff6cdcb
feat: implement the OS widget
jvsena42 Apr 30, 2026
ca68018
refactor: extract config screens
jvsena42 Apr 30, 2026
67aed9b
Merge branch 'feat/headlines-v61' into feat/blocks-v61
jvsena42 Apr 30, 2026
eeb59c9
doc: changelog entry
jvsena42 Apr 30, 2026
82c3c01
Merge branch 'feat/headlines-v61' into feat/blocks-v61
jvsena42 Apr 30, 2026
5dcfb18
fix: replace lazy column with column
jvsena42 Apr 30, 2026
24c9cf8
Merge branch 'feat/headlines-v61' into feat/blocks-v61
jvsena42 Apr 30, 2026
dd0eb13
Merge remote-tracking branch 'origin/feat/headlines-v61' into feat/bl…
jvsena42 Apr 30, 2026
b938208
chore: lint
jvsena42 Apr 30, 2026
826554a
fix: ui tests
jvsena42 Apr 30, 2026
e1ad9a9
Merge remote-tracking branch 'origin/feat/headlines-v61' into feat/bl…
jvsena42 May 4, 2026
38ecee6
chore: lint
jvsena42 May 4, 2026
9f92264
chore: lint
jvsena42 May 4, 2026
2419a73
fix: improve stability
jvsena42 May 4, 2026
e7ced77
feat: display cached articles on preview
jvsena42 May 4, 2026
b9c76a7
chore: lint
jvsena42 May 4, 2026
d478dcf
fix: use action callback for headlines widget click
jvsena42 May 4, 2026
ce5d795
chore: lint
jvsena42 May 4, 2026
d31c0b0
chore: lint
jvsena42 May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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 },
Expand All @@ -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()
}
Expand Down Expand Up @@ -115,6 +119,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
onClickShowFees = {},
onClickShowSource = {},
onClickReset = { resetClicked = true },
onClickPreview = {},
Expand Down Expand Up @@ -146,6 +151,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
Expand All @@ -167,6 +173,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = false,
showFees = false,
showSource = false
)

Expand All @@ -179,6 +186,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
Expand All @@ -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
Expand All @@ -209,6 +218,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = true,
showFees = false,
showSource = true
)

Expand All @@ -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 },
Expand All @@ -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)

Expand All @@ -265,7 +279,8 @@ class BlocksEditScreenTest {
date = "",
transactionCount = "",
size = "",
source = ""
source = "",
fees = "",
)

composeTestRule.setContent {
Expand All @@ -277,6 +292,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
Expand All @@ -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()
}
}
Expand All @@ -304,6 +320,7 @@ class BlocksEditScreenTest {
onClickShowDate = {},
onClickShowTransactions = {},
onClickShowSize = {},
onClickShowFees = {},
onClickShowSource = {},
onClickReset = {},
onClickPreview = {},
Expand All @@ -313,6 +330,7 @@ class BlocksEditScreenTest {
showDate = false,
showTransactions = false,
showSize = true,
showFees = false,
showSource = true
),
block = testBlock
Expand All @@ -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()
Expand All @@ -347,6 +366,7 @@ private fun BlockModel.getFieldValue(prefix: String): String {
"date" -> date
"transactions" -> transactionCount
"size" -> size
"fees" -> fees
"source" -> source
else -> ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -41,7 +42,6 @@ class BlocksPreviewContentTest {
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
onClickSave = { saveClicked = true },
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
Expand Down Expand Up @@ -96,7 +96,6 @@ class BlocksPreviewContentTest {
onClickEdit = { editClicked = true },
onClickDelete = { deleteClicked = true },
onClickSave = { saveClicked = true },
showWidgetTitles = false,
isBlocksWidgetEnabled = false,
blocksPreferences = defaultPreferences,
block = testBlock
Expand Down Expand Up @@ -137,7 +136,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = customPreferences,
block = testBlock
Expand All @@ -161,7 +159,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
Expand Down Expand Up @@ -197,7 +194,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
Expand Down Expand Up @@ -228,7 +224,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = false,
isBlocksWidgetEnabled = false,
blocksPreferences = minimalPreferences,
block = testBlock
Expand All @@ -253,7 +248,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = testBlock
Expand Down Expand Up @@ -285,7 +279,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = customPreferences,
block = testBlock
Expand All @@ -307,7 +300,6 @@ class BlocksPreviewContentTest {
onClickEdit = {},
onClickDelete = {},
onClickSave = {},
showWidgetTitles = true,
isBlocksWidgetEnabled = true,
blocksPreferences = defaultPreferences,
block = null
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,19 @@
android:resource="@xml/appwidget_info_headlines" />
</receiver>

<!-- Blocks Widget -->
<receiver
android:name=".appwidget.ui.blocks.BlocksGlanceReceiver"
android:exported="true"
android:label="@string/widgets__blocks__name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_info_blocks" />
</receiver>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<PriceDTO> =
withContext(ioDispatcher) {
Expand All @@ -26,4 +29,9 @@ class AppWidgetDataRepository @Inject constructor(
withContext(ioDispatcher) {
newsService.fetchData()
}

suspend fun fetchBlock(): Result<BlockDTO> =
withContext(ioDispatcher) {
blocksService.fetchData()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +33,7 @@ interface AppWidgetEntryPoint {
}

@Singleton
@Suppress("TooManyFunctions")
class AppWidgetPreferencesStore @Inject constructor(
@ApplicationContext private val context: Context,
) {
Expand Down Expand Up @@ -90,4 +92,8 @@ class AppWidgetPreferencesStore @Inject constructor(
)
}
}

suspend fun cacheBlock(block: BlockDTO) {
store.updateData { it.copy(cachedBlock = block) }
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,6 +67,7 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
private fun receiverClassFor(type: AppWidgetType): Class<out GlanceAppWidgetReceiver> = when (type) {
AppWidgetType.PRICE -> PriceGlanceReceiver::class.java
AppWidgetType.HEADLINES -> HeadlinesGlanceReceiver::class.java
AppWidgetType.BLOCKS -> BlocksGlanceReceiver::class.java
}
}

Expand Down Expand Up @@ -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)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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' " +
Expand Down
Loading
Loading