[Android] 2.2.1 - Better Widget

This commit is contained in:
2025-10-21 10:21:35 -06:00
parent fed9bab2ea
commit b7a3c98b2c
9 changed files with 278 additions and 349 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId = "com.atridad.ascently" applicationId = "com.atridad.ascently"
minSdk = 31 minSdk = 31
targetSdk = 36 targetSdk = 36
versionCode = 45 versionCode = 46
versionName = "2.2.0" versionName = "2.2.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -12,10 +12,11 @@ import com.atridad.ascently.MainActivity
import com.atridad.ascently.R import com.atridad.ascently.R
import com.atridad.ascently.data.database.AscentlyDatabase import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.repository.ClimbRepository import com.atridad.ascently.data.repository.ClimbRepository
import kotlinx.coroutines.* import com.atridad.ascently.widget.ClimbStatsWidgetProvider
import kotlinx.coroutines.flow.firstOrNull
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class SessionTrackingService : Service() { class SessionTrackingService : Service() {
@@ -71,12 +72,19 @@ class SessionTrackingService : Service() {
val sessionId = intent.getStringExtra(EXTRA_SESSION_ID) val sessionId = intent.getStringExtra(EXTRA_SESSION_ID)
serviceScope.launch { serviceScope.launch {
try { try {
val targetSession = when { val targetSession =
sessionId != null -> repository.getSessionById(sessionId) when {
else -> repository.getActiveSession() sessionId != null -> repository.getSessionById(sessionId)
} else -> repository.getActiveSession()
if (targetSession != null && targetSession.status == com.atridad.ascently.data.model.SessionStatus.ACTIVE) { }
val completed = with(com.atridad.ascently.data.model.ClimbSession) { targetSession.complete() } if (targetSession != null &&
targetSession.status ==
com.atridad.ascently.data.model.SessionStatus.ACTIVE
) {
val completed =
with(com.atridad.ascently.data.model.ClimbSession) {
targetSession.complete()
}
repository.updateSession(completed) repository.updateSession(completed)
} }
} finally { } finally {
@@ -97,45 +105,53 @@ class SessionTrackingService : Service() {
try { try {
createAndShowNotification(sessionId) createAndShowNotification(sessionId)
// Update widget when session tracking starts
ClimbStatsWidgetProvider.updateAllWidgets(this)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
notificationJob = serviceScope.launch { notificationJob =
try { serviceScope.launch {
if (!isNotificationActive()) { try {
delay(1000L) if (!isNotificationActive()) {
createAndShowNotification(sessionId) delay(1000L)
} createAndShowNotification(sessionId)
}
while (isActive) { while (isActive) {
delay(5000L) delay(5000L)
updateNotification(sessionId) updateNotification(sessionId)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
monitoringJob = serviceScope.launch { monitoringJob =
try { serviceScope.launch {
while (isActive) { try {
delay(10000L) while (isActive) {
delay(10000L)
if (!isNotificationActive()) {
updateNotification(sessionId) if (!isNotificationActive()) {
} updateNotification(sessionId)
}
val session = repository.getSessionById(sessionId)
if (session == null || session.status != com.atridad.ascently.data.model.SessionStatus.ACTIVE) { val session = repository.getSessionById(sessionId)
stopSessionTracking() if (session == null ||
break session.status !=
com.atridad.ascently.data.model.SessionStatus
.ACTIVE
) {
stopSessionTracking()
break
}
}
} catch (e: Exception) {
e.printStackTrace()
} }
} }
} catch (e: Exception) {
e.printStackTrace()
}
}
} }
private fun stopSessionTracking() { private fun stopSessionTracking() {
@@ -143,6 +159,8 @@ class SessionTrackingService : Service() {
monitoringJob?.cancel() monitoringJob?.cancel()
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf() stopSelf()
// Update widget when session tracking stops
ClimbStatsWidgetProvider.updateAllWidgets(this)
} }
private fun isNotificationActive(): Boolean { private fun isNotificationActive(): Boolean {
@@ -157,6 +175,8 @@ class SessionTrackingService : Service() {
private suspend fun updateNotification(sessionId: String) { private suspend fun updateNotification(sessionId: String) {
try { try {
createAndShowNotification(sessionId) createAndShowNotification(sessionId)
// Update widget when notification updates
ClimbStatsWidgetProvider.updateAllWidgets(this)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -172,67 +192,69 @@ class SessionTrackingService : Service() {
private fun createAndShowNotification(sessionId: String) { private fun createAndShowNotification(sessionId: String) {
try { try {
val session = runBlocking { val session = runBlocking { repository.getSessionById(sessionId) }
repository.getSessionById(sessionId) if (session == null ||
} session.status != com.atridad.ascently.data.model.SessionStatus.ACTIVE
if (session == null || session.status != com.atridad.ascently.data.model.SessionStatus.ACTIVE) { ) {
stopSessionTracking() stopSessionTracking()
return return
} }
val gym = runBlocking { val gym = runBlocking { repository.getGymById(session.gymId) }
repository.getGymById(session.gymId)
}
val attempts = runBlocking { val attempts = runBlocking {
repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList() repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList()
} }
val duration = session.startTime?.let { startTime -> val duration =
try { session.startTime?.let { startTime ->
val start = LocalDateTime.parse(startTime) try {
val now = LocalDateTime.now() val start = LocalDateTime.parse(startTime)
val totalSeconds = ChronoUnit.SECONDS.between(start, now) val now = LocalDateTime.now()
val hours = totalSeconds / 3600 val totalSeconds = ChronoUnit.SECONDS.between(start, now)
val minutes = (totalSeconds % 3600) / 60 val hours = totalSeconds / 3600
val seconds = totalSeconds % 60 val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
when { when {
hours > 0 -> "${hours}h ${minutes}m ${seconds}s" hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
minutes > 0 -> "${minutes}m ${seconds}s" minutes > 0 -> "${minutes}m ${seconds}s"
else -> "${totalSeconds}s" else -> "${totalSeconds}s"
}
} catch (_: Exception) {
"Active"
}
} }
} catch (_: Exception) { ?: "Active"
"Active"
}
} ?: "Active"
val notification = NotificationCompat.Builder(this, CHANNEL_ID) val notification =
.setContentTitle("Climbing Session Active") NotificationCompat.Builder(this, CHANNEL_ID)
.setContentText("${gym?.name ?: "Gym"}$duration${attempts.size} attempts") .setContentTitle("Climbing Session Active")
.setSmallIcon(R.drawable.ic_mountains) .setContentText(
.setOngoing(true) "${gym?.name ?: "Gym"}$duration${attempts.size} attempts"
.setAutoCancel(false) )
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(R.drawable.ic_mountains)
.setCategory(NotificationCompat.CATEGORY_SERVICE) .setOngoing(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setAutoCancel(false)
.setContentIntent(createOpenAppIntent()) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.addAction( .setCategory(NotificationCompat.CATEGORY_SERVICE)
R.drawable.ic_mountains, .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
"Open Session", .setContentIntent(createOpenAppIntent())
createOpenAppIntent() .addAction(
) R.drawable.ic_mountains,
.addAction( "Open Session",
android.R.drawable.ic_menu_close_clear_cancel, createOpenAppIntent()
"End Session", )
createStopPendingIntent(sessionId) .addAction(
) android.R.drawable.ic_menu_close_clear_cancel,
.build() "End Session",
createStopPendingIntent(sessionId)
)
.build()
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)
notificationManager.notify(NOTIFICATION_ID, notification) notificationManager.notify(NOTIFICATION_ID, notification)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
throw e throw e
@@ -240,41 +262,44 @@ class SessionTrackingService : Service() {
} }
private fun createOpenAppIntent(): PendingIntent { private fun createOpenAppIntent(): PendingIntent {
val intent = Intent(this, MainActivity::class.java).apply { val intent =
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP Intent(this, MainActivity::class.java).apply {
action = "OPEN_SESSION" flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
} action = "OPEN_SESSION"
}
return PendingIntent.getActivity( return PendingIntent.getActivity(
this, this,
0, 0,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
} }
private fun createStopPendingIntent(sessionId: String): PendingIntent { private fun createStopPendingIntent(sessionId: String): PendingIntent {
val intent = createStopIntent(this, sessionId) val intent = createStopIntent(this, sessionId)
return PendingIntent.getService( return PendingIntent.getService(
this, this,
1, 1,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
} }
private fun createNotificationChannel() { private fun createNotificationChannel() {
val channel = NotificationChannel( val channel =
CHANNEL_ID, NotificationChannel(
"Session Tracking", CHANNEL_ID,
NotificationManager.IMPORTANCE_DEFAULT "Session Tracking",
).apply { NotificationManager.IMPORTANCE_DEFAULT
description = "Shows active climbing session information" )
setShowBadge(false) .apply {
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC description = "Shows active climbing session information"
enableLights(false) setShowBadge(false)
enableVibration(false) lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
setSound(null, null) enableLights(false)
} enableVibration(false)
setSound(null, null)
}
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }

View File

@@ -11,6 +11,7 @@ import com.atridad.ascently.MainActivity
import com.atridad.ascently.R import com.atridad.ascently.R
import com.atridad.ascently.data.database.AscentlyDatabase import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.repository.ClimbRepository import com.atridad.ascently.data.repository.ClimbRepository
import java.time.LocalDate
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -48,53 +49,47 @@ class ClimbStatsWidgetProvider : AppWidgetProvider() {
val database = AscentlyDatabase.getDatabase(context) val database = AscentlyDatabase.getDatabase(context)
val repository = ClimbRepository(database, context) val repository = ClimbRepository(database, context)
// Fetch stats data // Get last 7 days date range (rolling period)
val today = LocalDate.now()
val sevenDaysAgo = today.minusDays(6) // Today + 6 days ago = 7 days total
// Fetch all sessions and attempts
val sessions = repository.getAllSessions().first() val sessions = repository.getAllSessions().first()
val problems = repository.getAllProblems().first()
val attempts = repository.getAllAttempts().first() val attempts = repository.getAllAttempts().first()
val gyms = repository.getAllGyms().first()
// Calculate stats // Filter for last 7 days across all gyms
val completedSessions = sessions.filter { it.endTime != null } val weekSessions =
sessions.filter { session ->
// Count problems that have been completed (have at least one successful attempt) try {
val completedProblems = val sessionDate = LocalDate.parse(session.date.substring(0, 10))
problems !sessionDate.isBefore(sevenDaysAgo) && !sessionDate.isAfter(today)
.filter { problem -> } catch (_: Exception) {
attempts.any { attempt -> false
attempt.problemId == problem.id && }
(attempt.result ==
com.atridad.ascently.data.model
.AttemptResult.SUCCESS ||
attempt.result ==
com.atridad.ascently.data.model
.AttemptResult.FLASH)
}
}
.size
val favoriteGym =
sessions.groupBy { it.gymId }.maxByOrNull { it.value.size }?.let {
(gymId, _) ->
gyms.find { it.id == gymId }?.name
} }
?: "No sessions yet"
val weekSessionIds = weekSessions.map { it.id }.toSet()
// Count total attempts this week
val totalAttempts =
attempts.count { attempt -> weekSessionIds.contains(attempt.sessionId) }
// Count sessions this week
val totalSessions = weekSessions.size
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
val views = RemoteViews(context.packageName, R.layout.widget_climb_stats) val views = RemoteViews(context.packageName, R.layout.widget_climb_stats)
views.setTextViewText( // Set weekly stats
R.id.widget_total_sessions, views.setTextViewText(R.id.widget_attempts_value, totalAttempts.toString())
completedSessions.size.toString() views.setTextViewText(R.id.widget_sessions_value, totalSessions.toString())
)
views.setTextViewText(
R.id.widget_problems_completed,
completedProblems.toString()
)
views.setTextViewText(R.id.widget_total_problems, problems.size.toString())
views.setTextViewText(R.id.widget_favorite_gym, favoriteGym)
val intent = Intent(context, MainActivity::class.java) val intent =
Intent(context, MainActivity::class.java).apply {
flags =
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val pendingIntent = val pendingIntent =
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
@@ -110,10 +105,8 @@ class ClimbStatsWidgetProvider : AppWidgetProvider() {
} catch (_: Exception) { } catch (_: Exception) {
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
val views = RemoteViews(context.packageName, R.layout.widget_climb_stats) val views = RemoteViews(context.packageName, R.layout.widget_climb_stats)
views.setTextViewText(R.id.widget_total_sessions, "0") views.setTextViewText(R.id.widget_attempts_value, "0")
views.setTextViewText(R.id.widget_problems_completed, "0") views.setTextViewText(R.id.widget_sessions_value, "0")
views.setTextViewText(R.id.widget_total_problems, "0")
views.setTextViewText(R.id.widget_favorite_gym, "No data")
val intent = Intent(context, MainActivity::class.java) val intent = Intent(context, MainActivity::class.java)
val pendingIntent = val pendingIntent =

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM10,17L5,12L6.41,10.59L10,14.17L17.59,6.58L19,8L10,17Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M9,11.24V7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5C7,9.06 7.79,10.43 9,11.24zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11H13v-6C13,6.67 12.33,6 11.5,6S10,6.67 10,7.5v10.74l-3.43,-0.72c-0.08,-0.01 -0.15,-0.03 -0.24,-0.03c-0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8l4.94,4.94C9.96,23.83 10.34,24 10.75,24h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02,-0.2C19.75,16.63 19.37,16.09 18.84,15.87z"/>
</vector>

View File

@@ -5,190 +5,84 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/widget_background" android:background="@drawable/widget_background"
android:orientation="vertical" android:orientation="vertical"
android:padding="12dp"> android:padding="12dp"
android:gravity="center">
<!-- Header --> <!-- Header with icon and "Weekly" text -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp"> android:layout_marginBottom="12dp">
<ImageView <ImageView
android:layout_width="24dp" android:layout_width="28dp"
android:layout_height="24dp" android:layout_height="28dp"
android:src="@drawable/ic_mountains" android:src="@drawable/ic_mountains"
android:tint="@color/widget_primary" android:tint="@color/widget_primary"
android:layout_marginEnd="8dp" /> android:layout_marginEnd="8dp"
android:contentDescription="Ascently icon" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Ascently"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/widget_text_primary" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Climbing Stats" android:text="Weekly"
android:textSize="12sp" android:textSize="18sp"
android:textColor="@color/widget_text_secondary" /> android:textColor="@color/widget_text_primary" />
</LinearLayout> </LinearLayout>
<!-- Stats Grid --> <!-- Attempts Row -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1" android:orientation="horizontal"
android:orientation="vertical" android:gravity="center_vertical"
android:gravity="center"> android:layout_marginBottom="12dp">
<!-- Top Row --> <ImageView
<LinearLayout android:layout_width="32dp"
android:layout_width="match_parent" android:layout_height="32dp"
android:layout_height="0dp" android:src="@drawable/ic_circle_filled"
android:layout_weight="1" android:tint="@color/widget_primary"
android:orientation="horizontal" android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"> android:contentDescription="Attempts icon" />
<!-- Sessions Card --> <TextView
<LinearLayout android:id="@+id/widget_attempts_value"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1" android:text="0"
android:orientation="vertical" android:textSize="40sp"
android:gravity="center" android:textStyle="bold"
android:background="@drawable/widget_stat_card_background" android:textColor="@color/widget_text_primary" />
android:layout_marginEnd="4dp"
android:padding="12dp">
<TextView </LinearLayout>
android:id="@+id/widget_total_sessions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/widget_primary" />
<TextView <!-- Sessions Row -->
android:layout_width="wrap_content" <LinearLayout
android:layout_height="wrap_content" android:layout_width="match_parent"
android:text="Sessions" android:layout_height="wrap_content"
android:textSize="12sp" android:orientation="horizontal"
android:textColor="@color/widget_text_secondary" android:gravity="center_vertical">
android:layout_marginTop="2dp" />
</LinearLayout> <ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_play_arrow_24"
android:tint="@color/widget_primary"
android:layout_marginEnd="12dp"
android:contentDescription="Sessions icon" />
<!-- Problems Card --> <TextView
<LinearLayout android:id="@+id/widget_sessions_value"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1" android:text="0"
android:orientation="vertical" android:textSize="40sp"
android:gravity="center" android:textStyle="bold"
android:background="@drawable/widget_stat_card_background" android:textColor="@color/widget_text_primary" />
android:layout_marginStart="4dp"
android:padding="12dp">
<TextView
android:id="@+id/widget_problems_completed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/widget_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Completed"
android:textSize="12sp"
android:textColor="@color/widget_text_secondary"
android:layout_marginTop="2dp" />
</LinearLayout>
</LinearLayout>
<!-- Bottom Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<!-- Success Rate Card -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/widget_stat_card_background"
android:layout_marginEnd="4dp"
android:padding="12dp">
<TextView
android:id="@+id/widget_total_problems"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/widget_secondary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Problems"
android:textSize="12sp"
android:textColor="@color/widget_text_secondary"
android:layout_marginTop="2dp" />
</LinearLayout>
<!-- Favorite Gym Card -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/widget_stat_card_background"
android:layout_marginStart="4dp"
android:padding="12dp">
<TextView
android:id="@+id/widget_favorite_gym"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No gyms"
android:textSize="13sp"
android:textStyle="bold"
android:textColor="@color/widget_accent"
android:gravity="center"
android:maxLines="2"
android:ellipsize="end" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Favorite"
android:textSize="12sp"
android:textColor="@color/widget_text_secondary"
android:layout_marginTop="2dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -12,5 +12,5 @@
<string name="shortcut_end_session_disabled">No active session to end</string> <string name="shortcut_end_session_disabled">No active session to end</string>
<!-- Widget --> <!-- Widget -->
<string name="widget_description">View your climbing stats at a glance</string> <string name="widget_description">View your weekly climbing stats</string>
</resources> </resources>

View File

@@ -3,15 +3,14 @@
android:description="@string/widget_description" android:description="@string/widget_description"
android:initialKeyguardLayout="@layout/widget_climb_stats" android:initialKeyguardLayout="@layout/widget_climb_stats"
android:initialLayout="@layout/widget_climb_stats" android:initialLayout="@layout/widget_climb_stats"
android:minWidth="250dp" android:minWidth="110dp"
android:minHeight="180dp" android:minHeight="110dp"
android:maxResizeWidth="110dp"
android:maxResizeHeight="110dp"
android:previewImage="@drawable/ic_mountains" android:previewImage="@drawable/ic_mountains"
android:previewLayout="@layout/widget_climb_stats" android:previewLayout="@layout/widget_climb_stats"
android:resizeMode="horizontal|vertical" android:resizeMode="none"
android:targetCellWidth="4" android:targetCellWidth="2"
android:targetCellHeight="2" android:targetCellHeight="2"
android:updatePeriodMillis="1800000" android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen" android:widgetCategory="home_screen" />
android:widgetFeatures="reconfigurable"
android:maxResizeWidth="320dp"
android:maxResizeHeight="240dp" />