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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ All notable changes to mobileGF2logger are documented here.

### Changed

- Refine the Home capture card and active-Platoon selector, use cohesive
rounded dialogs, compact packet-history settings, and replace queued transient
messages so repeated actions do not leave stale feedback behind.
- Quarantine up to 32 decoded payloads per TCP flow until both its supported
Android client and valid Platoon identity are known; unverified flows never
enter management storage.
Expand All @@ -42,6 +45,8 @@ All notable changes to mobileGF2logger are documented here.

### Fixed

- Improve weekly evidence guidance and restore consistent spacing around weekly
notes and member-detail fields in both light and dark themes.
- Resolve all six review findings from v2.3.3: clean flow metadata without a
parser, order known same-day joins by instant, bound manual weekly notes,
preserve captured member names, replace timezone-derived history atomically,
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/dev/gf2log/app/DateTimePickerInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal class DateTimePickerInput(
}
private val timeInput = pickerField(context.getString(R.string.time_optional)) {
if (selection.date == null) {
Toast.makeText(context, R.string.select_date_first, Toast.LENGTH_SHORT).show()
TransientMessage.show(context, R.string.select_date_first)
} else {
showTimePicker()
}
Expand Down
40 changes: 24 additions & 16 deletions app/src/main/java/dev/gf2log/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -299,41 +299,49 @@ class MainActivity : LocalizedActivity() {
textSize = 22f
setTextColor(getColor(R.color.success_text))
setTypeface(typeface, Typeface.BOLD)
setPadding(0, dp(2), 0, 0)
setPadding(0, dp(4), 0, 0)
}
addView(captureStateText, matchWidth())
captureStatusText = TextView(context).apply {
textSize = 13f
setTextColor(getColor(R.color.text_secondary))
}
addView(captureStatusText, matchWidth())
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply {
}, LinearLayout.LayoutParams(dp(124), ViewGroup.LayoutParams.WRAP_CONTENT).apply {
marginEnd = dp(8)
})
addView(
PlatoonProfileSelector.controls(this@MainActivity, compact = true),
PlatoonProfileSelector.controls(
this@MainActivity,
compact = true,
showManageButton = false,
),
LinearLayout.LayoutParams(
dp(200),
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
),
1f,
).apply { topMargin = dp(18) },
)
}, matchWidth())
addView(LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.TOP
gravity = Gravity.BOTTOM
addView(TextView(context).apply {
text = getString(R.string.capture_target)
textSize = 12f
setTextColor(getColor(R.color.text_secondary))
setPadding(0, dp(4), 0, 0)
setPadding(0, 0, 0, dp(4))
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
addView(ImageButton(context).apply {
setImageResource(R.drawable.ic_info_outline)
contentDescription = getString(R.string.target_package_info)
useModernIconStyle()
setOnClickListener(::showTargetPackageInfo)
}, LinearLayout.LayoutParams(dp(48), dp(48)))
}, matchWidth())
}, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dp(48),
))
addView(fixedTargetField(SupportedGamePackages.HAOPLAY), matchWidth())
addView(
fixedTargetField(SupportedGamePackages.DARKWINTER),
Expand Down Expand Up @@ -509,11 +517,14 @@ class MainActivity : LocalizedActivity() {
},
)
val status = CaptureStatus.read()
captureStatusText.text = when (status) {
"Capture is stopped" -> getString(R.string.capture_stopped_detail)
"Preparing capture" -> getString(R.string.status_preparing)
val detail = when {
status.startsWith("Capturing only ") -> ""
status == "Capture is stopped" -> getString(R.string.capture_stopped_detail)
status == "Preparing capture" -> getString(R.string.status_preparing)
else -> status
}
captureStatusText.text = detail
captureStatusText.visibility = if (detail.isBlank()) View.GONE else View.VISIBLE
captureStateText.contentDescription = captureStateText.text
captureStatusText.contentDescription = captureStatusText.text
val busy = starting || running
Expand Down Expand Up @@ -638,10 +649,7 @@ class MainActivity : LocalizedActivity() {
}

private fun showNoPlatoonMessage() {
AlertDialog.Builder(this)
.setMessage(R.string.no_platoon_detected_detail)
.setPositiveButton(android.R.string.ok, null)
.show()
TransientMessage.show(this, R.string.no_platoon_detected_detail, android.widget.Toast.LENGTH_LONG)
}

private fun showCsvImportSelector() {
Expand Down Expand Up @@ -961,7 +969,7 @@ class MainActivity : LocalizedActivity() {
}
val scope = requireActiveScope() ?: return
if (!CsvImportCheckpointManager(this, scope).canUndo()) {
statusText.text = getString(R.string.no_csv_import_checkpoint)
TransientMessage.show(this, R.string.no_csv_import_checkpoint)
return
}
AlertDialog.Builder(this)
Expand Down
31 changes: 17 additions & 14 deletions app/src/main/java/dev/gf2log/app/MemberDetailActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ class MemberDetailActivity : LocalizedActivity() {
setPadding(0, dp(4), 0, dp(8))
}, matchWidth())
addView(nameInput, matchWidth())
addView(noteInput, matchWidth())
addView(noteInput, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
).apply { topMargin = dp(8) })
addView(LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
addView(Button(context).apply {
Expand All @@ -85,11 +88,11 @@ class MemberDetailActivity : LocalizedActivity() {
noteInput.text.toString(),
)
}.getOrDefault(false)
Toast.makeText(
TransientMessage.show(
this@MemberDetailActivity,
getString(if (saved) R.string.saved else R.string.save_failed),
Toast.LENGTH_SHORT,
).show()
)
}
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply {
marginEnd = dp(4)
Expand Down Expand Up @@ -138,18 +141,18 @@ class MemberDetailActivity : LocalizedActivity() {
}.getOrDefault(false)
if (deleted) {
dialog.dismiss()
Toast.makeText(
TransientMessage.show(
this@MemberDetailActivity,
R.string.member_deleted,
Toast.LENGTH_SHORT,
).show()
)
finish()
} else {
Toast.makeText(
TransientMessage.show(
this@MemberDetailActivity,
R.string.member_delete_failed,
Toast.LENGTH_SHORT,
).show()
)
}
}
}
Expand Down Expand Up @@ -246,11 +249,11 @@ class MemberDetailActivity : LocalizedActivity() {
note.text.toString(),
)
}.getOrDefault(false)
Toast.makeText(
TransientMessage.show(
this,
getString(if (saved) R.string.saved else R.string.invalid_date),
Toast.LENGTH_SHORT,
).show()
)
if (saved) {
dialog.dismiss()
render()
Expand Down Expand Up @@ -280,18 +283,18 @@ class MemberDetailActivity : LocalizedActivity() {
if (deleted) {
confirmation.dismiss()
editor.dismiss()
Toast.makeText(
TransientMessage.show(
this@MemberDetailActivity,
R.string.membership_period_deleted,
Toast.LENGTH_SHORT,
).show()
)
render()
} else {
Toast.makeText(
TransientMessage.show(
this@MemberDetailActivity,
R.string.membership_period_delete_failed,
Toast.LENGTH_SHORT,
).show()
)
}
}
}
Expand Down Expand Up @@ -346,7 +349,7 @@ class MemberDetailActivity : LocalizedActivity() {
dialog.dismiss()
render()
} else {
Toast.makeText(this, R.string.invalid_date, Toast.LENGTH_SHORT).show()
TransientMessage.show(this, R.string.invalid_date)
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/dev/gf2log/app/ModernFeedback.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.gf2log.app

import android.app.AlertDialog
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes

/** Keeps app-owned dialogs visually consistent while preserving Android dialog behavior. */
internal fun AlertDialog.applyModernDialogStyle(): AlertDialog = apply {
window?.setBackgroundDrawable(ModernUi.dialogBackground(context))
getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(context.getColor(R.color.accent))
getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(context.getColor(R.color.text_secondary))
getButton(AlertDialog.BUTTON_NEUTRAL)?.setTextColor(context.getColor(R.color.text_secondary))
}

internal fun AlertDialog.Builder.showModern(): AlertDialog = show().applyModernDialogStyle()

/**
* Replaces the currently visible transient message instead of extending a long Toast queue.
* Android does not expose its internal queue, so cancelling the prior app-owned Toast is the
* deterministic way to keep rapid repeated actions responsive.
*/
internal object TransientMessage {
private var active: Toast? = null

@Synchronized
fun show(context: Context, message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
active?.cancel()
active = Toast.makeText(context.applicationContext, message, duration).also(Toast::show)
}

fun show(context: Context, @StringRes message: Int, duration: Int = Toast.LENGTH_SHORT) {
show(context, context.getString(message), duration)
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/dev/gf2log/app/ModernUi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ object ModernUi {
SECONDARY,
TERTIARY,
NAVIGATION,
SELECTOR,
DESTRUCTIVE,
DESTRUCTIVE_TEXT,
}
Expand All @@ -51,6 +52,13 @@ object ModernUi {
cornerRadius = context.dp(16).toFloat()
}

/** Theme-aware surface used by app-owned dialogs without replacing native semantics. */
fun dialogBackground(context: Context): GradientDrawable = GradientDrawable().apply {
setColor(context.getColor(R.color.surface))
cornerRadius = context.dp(20).toFloat()
setStroke(context.dp(1), context.getColor(R.color.outline))
}

/** Flat list navigation used by dashboard utilities and grouped Settings rows. */
fun listRow(
context: Context,
Expand Down Expand Up @@ -414,6 +422,16 @@ object ModernUi {
insetVertical = 4,
radiusDp = 12,
)
ControlRole.SELECTOR -> ButtonColors(
normal = R.color.surface,
pressed = R.color.surface_pressed,
disabled = R.color.surface_variant,
stroke = R.color.outline_strong,
pressedStroke = R.color.accent,
text = R.color.text_primary,
insetVertical = 4,
radiusDp = 12,
)
ControlRole.SECONDARY -> ButtonColors(
normal = R.color.surface,
pressed = R.color.surface_pressed,
Expand Down
Loading
Loading