Skip to content
Open
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
11 changes: 11 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,17 @@
android:resource="@xml/dashboard_widget_info" />
</receiver>

<receiver android:name=".BeeCountConsumptionRhythmWidgetProvider"
android:label="@string/widget_consumption_rhythm_name" android:exported="true">
<intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/consumption_rhythm_widget_info" />
</receiver>
<receiver android:name=".BeeCountBeeTrailWidgetProvider"
android:label="@string/widget_bee_trail_name" android:exported="true">
<intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/bee_trail_widget_info" />
</receiver>

</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.tntlikely.beecount

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File

/** 小号记账连续蜂迹组件,展示 Flutter 离屏渲染的 `widget_beeTrail_small`。 */
class BeeCountBeeTrailWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, manager: AppWidgetManager, ids: IntArray, data: SharedPreferences) {
ids.forEach { id ->
val views = RemoteViews(context.packageName, R.layout.bee_trail_widget)
val path = data.getString("widget_beeTrail_small", null)
val bitmap = path?.let { if (File(it).exists()) BitmapFactory.decodeFile(it) else null }
if (bitmap != null) views.setImageViewBitmap(R.id.widget_image, bitmap)
else views.setImageViewResource(R.id.widget_image, R.mipmap.ic_launcher)
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
?: Intent(Intent.ACTION_VIEW, Uri.parse("beecount://open?page=transactions"))
intent.data = Uri.parse("beecount://open?page=transactions")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
views.setOnClickPendingIntent(
R.id.widget_click,
PendingIntent.getActivity(context, id, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
)
manager.updateAppWidget(id, views)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.tntlikely.beecount

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.net.Uri
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File

/** 中号消费节奏组件,展示 Flutter 离屏渲染的 `widget_consumptionRhythm_medium`。 */
class BeeCountConsumptionRhythmWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, manager: AppWidgetManager, ids: IntArray, data: SharedPreferences) {
ids.forEach { id ->
val views = RemoteViews(context.packageName, R.layout.consumption_rhythm_widget)
val path = data.getString("widget_consumptionRhythm_medium", null)
val bitmap = path?.let { if (File(it).exists()) BitmapFactory.decodeFile(it) else null }
if (bitmap != null) views.setImageViewBitmap(R.id.widget_image, bitmap)
else views.setImageViewResource(R.id.widget_image, R.mipmap.ic_launcher)
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
?: Intent(Intent.ACTION_VIEW, Uri.parse("beecount://open?page=statistics"))
intent.data = Uri.parse("beecount://open?page=statistics")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
views.setOnClickPendingIntent(
R.id.widget_click,
PendingIntent.getActivity(context, id, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
)
manager.updateAppWidget(id, views)
}
}
}
5 changes: 5 additions & 0 deletions android/app/src/main/res/layout/bee_trail_widget.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView android:id="@+id/widget_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitCenter" android:contentDescription="Widget content" />
<FrameLayout android:id="@+id/widget_click" android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
5 changes: 5 additions & 0 deletions android/app/src/main/res/layout/consumption_rhythm_widget.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView android:id="@+id/widget_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitCenter" android:contentDescription="Widget content" />
<FrameLayout android:id="@+id/widget_click" android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
4 changes: 4 additions & 0 deletions android/app/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string name="widget_recent_name">Recent (Medium)</string>
<string name="widget_recent_large_name">Recent (Large)</string>
<string name="widget_dashboard_name">Dashboard</string>
<string name="widget_consumption_rhythm_name">Spending Rhythm</string>
<string name="widget_bee_trail_name">Record Bee Trail</string>

<!-- Widget descriptions (android:description) -->
<string name="widget_description">Today\'s and this month\'s income &amp; expenses</string>
Expand All @@ -25,4 +27,6 @@
<string name="widget_budget_description">Track budget progress at a glance</string>
<string name="widget_recent_description">A quick look at your latest transactions</string>
<string name="widget_dashboard_description">Income, trend, and recent activity in one view</string>
<string name="widget_consumption_rhythm_description">See your spending pace across the last 30 days</string>
<string name="widget_bee_trail_description">Build a daily recording habit with a honeycomb trail</string>
</resources>
7 changes: 7 additions & 0 deletions android/app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="widget_consumption_rhythm_name">소비 리듬</string>
<string name="widget_bee_trail_name">기록 꿀벌 궤적</string>
<string name="widget_consumption_rhythm_description">최근 30일의 소비 흐름을 한눈에 확인하세요</string>
<string name="widget_bee_trail_description">벌집 칸으로 매일 기록하는 습관을 만들어 보세요</string>
</resources>
4 changes: 4 additions & 0 deletions android/app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string name="widget_recent_name">最近交易·中</string>
<string name="widget_recent_large_name">最近交易·大</string>
<string name="widget_dashboard_name">綜合儀表盤</string>
<string name="widget_consumption_rhythm_name">消費節奏</string>
<string name="widget_bee_trail_name">記帳連續蜂跡</string>

<!-- 小組件描述(android:description) -->
<string name="widget_description">查看今日和本月的收支情況</string>
Expand All @@ -25,4 +27,6 @@
<string name="widget_budget_description">預算進度即時掌握</string>
<string name="widget_recent_description">快速查看最近幾筆帳單</string>
<string name="widget_dashboard_description">收支、趨勢與最近交易一屏看盡</string>
<string name="widget_consumption_rhythm_description">近 30 天消費節奏一眼看清</string>
<string name="widget_bee_trail_description">用蜂巢格養成每日記帳習慣</string>
</resources>
4 changes: 4 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<string name="widget_recent_name">最近交易·中</string>
<string name="widget_recent_large_name">最近交易·大</string>
<string name="widget_dashboard_name">综合仪表盘</string>
<string name="widget_consumption_rhythm_name">消费节奏</string>
<string name="widget_bee_trail_name">记账连续蜂迹</string>

<!-- 桌面小组件:选择器里名称下方的一句描述(android:description)。 -->
<string name="widget_description">查看今日和本月的收支情况</string>
Expand All @@ -25,6 +27,8 @@
<string name="widget_budget_description">预算进度实时掌握</string>
<string name="widget_recent_description">快速查看最近几笔账单</string>
<string name="widget_dashboard_description">收支、趋势与最近交易一屏看尽</string>
<string name="widget_consumption_rhythm_description">近 30 天消费节奏一眼看清</string>
<string name="widget_bee_trail_description">用蜂巢格养成每日记账习惯</string>

<!-- 无障碍服务相关字符串由 flavor 特定的 strings.xml 定义 -->
<!-- dev: android/app/src/dev/res/values/strings.xml -->
Expand Down
5 changes: 5 additions & 0 deletions android/app/src/main/res/xml/bee_trail_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="155dp" android:minHeight="155dp" android:updatePeriodMillis="1800000"
android:initialLayout="@layout/bee_trail_widget"
android:widgetCategory="home_screen" android:description="@string/widget_bee_trail_description" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="364dp" android:minHeight="169dp" android:updatePeriodMillis="1800000"
android:initialLayout="@layout/consumption_rhythm_widget"
android:widgetCategory="home_screen" android:description="@string/widget_consumption_rhythm_description" />
100 changes: 100 additions & 0 deletions ios/BeeCountWidget/BeeCountBehaviorWidgets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import WidgetKit
import SwiftUI
import UIKit

private enum BehaviorWidgetCopy {
static func localized(
simplifiedChinese: String,
traditionalChinese: String,
english: String,
korean: String
) -> String {
let locale = Locale.current
switch locale.languageCode {
case "en": return english
case "ko": return korean
case "zh" where locale.scriptCode == "Hant" || locale.regionCode == "TW" || locale.regionCode == "HK":
return traditionalChinese
default: return simplifiedChinese
}
}
}

private struct BehaviorWidgetEntry: TimelineEntry {
let date: Date
let imagePath: String
}

private struct BehaviorWidgetProvider: TimelineProvider {
let imageKey: String

private func entry() -> BehaviorWidgetEntry {
let path = UserDefaults(suiteName: "group.com.tntlikely.beecount")?.string(forKey: imageKey) ?? ""
return BehaviorWidgetEntry(date: Date(), imagePath: path)
}

func placeholder(in context: Context) -> BehaviorWidgetEntry { entry() }
func getSnapshot(in context: Context, completion: @escaping (BehaviorWidgetEntry) -> ()) { completion(entry()) }
func getTimeline(in context: Context, completion: @escaping (Timeline<BehaviorWidgetEntry>) -> ()) {
let next = Calendar.current.date(byAdding: .minute, value: 30, to: Date())!
completion(Timeline(entries: [entry()], policy: .after(next)))
}
}

private struct BehaviorWidgetEntryView: View {
let entry: BehaviorWidgetEntry
let destination: URL
let symbol: String
let label: String

var body: some View {
if let image = UIImage(contentsOfFile: entry.imagePath) {
Link(destination: destination) {
Image(uiImage: image).resizable().scaledToFill().frame(maxWidth: .infinity, maxHeight: .infinity).clipped()
}
} else {
ZStack {
Color(red: 1.0, green: 0.76, blue: 0.03)
VStack { Image(systemName: symbol).font(.system(size: 28)); Text(label).font(.system(size: 13, weight: .semibold)) }.foregroundColor(.white)
}.widgetURL(destination)
}
}
}

struct BeeCountConsumptionRhythmWidget: Widget {
let kind = "BeeCountConsumptionRhythmWidget"
private var title: String {
BehaviorWidgetCopy.localized(simplifiedChinese: "消费节奏", traditionalChinese: "消費節奏", english: "Spending Rhythm", korean: "소비 리듬")
}
private var description: String {
BehaviorWidgetCopy.localized(simplifiedChinese: "近 30 天消费节奏一眼看清", traditionalChinese: "近 30 天消費節奏一眼看清", english: "See your spending pace across the last 30 days", korean: "최근 30일의 소비 흐름을 한눈에 확인하세요")
}
var body: some WidgetConfiguration {
let widgetTitle = title
let widgetDescription = description
return StaticConfiguration(kind: kind, provider: BehaviorWidgetProvider(imageKey: "widget_consumptionRhythm_medium")) { entry in
BehaviorWidgetEntryView(entry: entry, destination: URL(string: "beecount://open?page=statistics")!, symbol: "chart.bar.fill", label: widgetTitle)
}
.configurationDisplayName(widgetTitle).description(widgetDescription)
.supportedFamilies([.systemMedium]).contentMarginsDisabled()
}
}

struct BeeCountBeeTrailWidget: Widget {
let kind = "BeeCountBeeTrailWidget"
private var title: String {
BehaviorWidgetCopy.localized(simplifiedChinese: "记账连续蜂迹", traditionalChinese: "記帳連續蜂跡", english: "Record Bee Trail", korean: "기록 꿀벌 궤적")
}
private var description: String {
BehaviorWidgetCopy.localized(simplifiedChinese: "用蜂巢格养成每日记账习惯", traditionalChinese: "用蜂巢格養成每日記帳習慣", english: "Build a daily recording habit with a honeycomb trail", korean: "벌집 칸으로 매일 기록하는 습관을 만들어 보세요")
}
var body: some WidgetConfiguration {
let widgetTitle = title
let widgetDescription = description
return StaticConfiguration(kind: kind, provider: BehaviorWidgetProvider(imageKey: "widget_beeTrail_small")) { entry in
BehaviorWidgetEntryView(entry: entry, destination: URL(string: "beecount://open?page=transactions")!, symbol: "hexagon.fill", label: widgetTitle)
}
.configurationDisplayName(widgetTitle).description(widgetDescription)
.supportedFamilies([.systemSmall]).contentMarginsDisabled()
}
}
2 changes: 2 additions & 0 deletions ios/BeeCountWidget/BeeCountWidgetBundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ struct BeeCountWidgetBundle: WidgetBundle {
BeeCountBudgetWidget()
BeeCountRecentWidget()
BeeCountDashboardWidget()
BeeCountConsumptionRhythmWidget()
BeeCountBeeTrailWidget()
}
}
12 changes: 12 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,18 @@
"widgetGalleryDashboardTitle": "Dashboard",
"widgetDashboardTitle": "This Month",
"widgetGalleryDashboardDesc": "Income, trend and recent transactions in one view",
"widgetConsumptionRhythmTitle": "Spending Rhythm",
"widgetConsumptionRhythmRange": "Last 30 days",
"widgetConsumptionRhythmStable": "Spending is steady",
"widgetConsumptionRhythmIncrease": "Faster than last week",
"widgetConsumptionRhythmDecrease": "Steadier than last week",
"widgetConsumptionRhythmEmpty": "No spending in the last 30 days",
"widgetBeeTrailTitle": "Record Bee Trail",
"widgetBeeTrailStreakSuffix": "days",
"widgetBeeTrailCompletion": "28-day completion",
"widgetBeeTrailEmpty": "Add a record to light the first cell",
"widgetGalleryConsumptionRhythmDesc": "See your spending pace across the last 30 days",
"widgetGalleryBeeTrailDesc": "Build a gentle daily recording habit",
"widgetSizeSmall": "Small",
"widgetSizeMedium": "Medium",
"widgetSizeLarge": "Large",
Expand Down
12 changes: 12 additions & 0 deletions lib/l10n/app_ko.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,18 @@
"welcomeCloudSyncFeature3": "또는 iCloud / WebDAV / Supabase / S3 중 선택",
"widgetManagement": "홈 화면 위젯",
"widgetManagementDesc": "홈 화면에서 수입과 지출을 빠르게 확인하세요",
"widgetConsumptionRhythmTitle": "소비 리듬",
"widgetConsumptionRhythmRange": "최근 30일",
"widgetConsumptionRhythmStable": "소비가 안정적이에요",
"widgetConsumptionRhythmIncrease": "지난주보다 빨라요",
"widgetConsumptionRhythmDecrease": "지난주보다 안정적이에요",
"widgetConsumptionRhythmEmpty": "최근 30일 지출이 없어요",
"widgetBeeTrailTitle": "기록 꿀벌 궤적",
"widgetBeeTrailStreakSuffix": "일",
"widgetBeeTrailCompletion": "최근 28일 완료율",
"widgetBeeTrailEmpty": "첫 칸을 밝히려면 기록을 추가하세요",
"widgetGalleryConsumptionRhythmDesc": "최근 30일의 소비 흐름을 확인하세요",
"widgetGalleryBeeTrailDesc": "벌집 칸으로 매일 기록하는 습관을 만드세요",
"widgetPreview": "위젯 미리보기",
"widgetPreviewDesc": "위젯은 현재 가계부의 실제 데이터를 자동으로 표시하며, 테마 색상은 앱 설정을 따릅니다",
"howToAddWidget": "위젯 추가 방법",
Expand Down
72 changes: 72 additions & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7718,6 +7718,78 @@ abstract class AppLocalizations {
/// **'Income, trend and recent transactions in one view'**
String get widgetGalleryDashboardDesc;

/// No description provided for @widgetConsumptionRhythmTitle.
///
/// In en, this message translates to:
/// **'Spending Rhythm'**
String get widgetConsumptionRhythmTitle;

/// No description provided for @widgetConsumptionRhythmRange.
///
/// In en, this message translates to:
/// **'Last 30 days'**
String get widgetConsumptionRhythmRange;

/// No description provided for @widgetConsumptionRhythmStable.
///
/// In en, this message translates to:
/// **'Spending is steady'**
String get widgetConsumptionRhythmStable;

/// No description provided for @widgetConsumptionRhythmIncrease.
///
/// In en, this message translates to:
/// **'Faster than last week'**
String get widgetConsumptionRhythmIncrease;

/// No description provided for @widgetConsumptionRhythmDecrease.
///
/// In en, this message translates to:
/// **'Steadier than last week'**
String get widgetConsumptionRhythmDecrease;

/// No description provided for @widgetConsumptionRhythmEmpty.
///
/// In en, this message translates to:
/// **'No spending in the last 30 days'**
String get widgetConsumptionRhythmEmpty;

/// No description provided for @widgetBeeTrailTitle.
///
/// In en, this message translates to:
/// **'Record Bee Trail'**
String get widgetBeeTrailTitle;

/// No description provided for @widgetBeeTrailStreakSuffix.
///
/// In en, this message translates to:
/// **'days'**
String get widgetBeeTrailStreakSuffix;

/// No description provided for @widgetBeeTrailCompletion.
///
/// In en, this message translates to:
/// **'28-day completion'**
String get widgetBeeTrailCompletion;

/// No description provided for @widgetBeeTrailEmpty.
///
/// In en, this message translates to:
/// **'Add a record to light the first cell'**
String get widgetBeeTrailEmpty;

/// No description provided for @widgetGalleryConsumptionRhythmDesc.
///
/// In en, this message translates to:
/// **'See your spending pace across the last 30 days'**
String get widgetGalleryConsumptionRhythmDesc;

/// No description provided for @widgetGalleryBeeTrailDesc.
///
/// In en, this message translates to:
/// **'Build a gentle daily recording habit'**
String get widgetGalleryBeeTrailDesc;

/// No description provided for @widgetSizeSmall.
///
/// In en, this message translates to:
Expand Down
Loading