diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml
index 356c712..6664c01 100644
--- a/.github/workflows/build-apk.yml
+++ b/.github/workflows/build-apk.yml
@@ -1,60 +1,44 @@
name: Flutter APK Build
on:
- # যখনই main ব্রাঞ্চে কোড পুশ করা হবে
- push:
- branches: [ main ]
- # ইচ্ছে করলে Pull Request এর জন্যও চালানো যায়
- pull_request:
- branches: [ main ]
+ workflow_dispatch:
jobs:
build-android:
name: Build Android APK
- # Linux রানার ব্যবহার করুন (এটা ফ্রি এবং দ্রুত) [citation:4]
runs-on: ubuntu-latest
-
+
steps:
- # ১. আপনার রিপোজিটরির কোড ডাউনলোড করা [citation:5]
- name: Checkout code
uses: actions/checkout@v4
- # ২. Java সেটআপ করা (Flutter Android বিল্ডের জন্য বাধ্যতামূলক) [citation:5][citation:8]
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- # ৩. Flutter এনভায়রনমেন্ট সেটআপ করা (ক্যাশিং চালু করে, যেন পরের বার দ্রুত কাজ করে) [citation:5][citation:7]
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
- # এই অপশনটা Flutter আর Pub ডিপেন্ডেন্সি ক্যাশ করে দেয়, বিল্ড স্পিড বাড়ায়
- cache: true
+ cache: true
- # ৪. প্রোজেক্টের ডিপেন্ডেন্সি ইনস্টল করা [citation:4][citation:8]
- name: Get dependencies
run: flutter pub get
- # ৫. (অপশনাল) কোড চেক করা - প্রোডাকশনের জন্য ভালো অভ্যাস [citation:1][citation:4]
- name: Analyze code
run: flutter analyze
- # ৬. (অপশনাল) টেস্ট রান করা [citation:1][citation:4]
- name: Run tests
run: flutter test
- # ৭. রিলিজ APK বিল্ড করা [citation:5]
- name: Build APK
run: flutter build apk --release
- # ৮. বিল্ড আউটপুট জমা রাখা (আপনি GitHub থেকেই ডাউনলোড করতে পারবেন) [citation:1][citation:8]
- name: Upload APK Artifact
uses: actions/upload-artifact@v4
with:
name: release-apk
path: build/app/outputs/flutter-apk/app-release.apk
- # ৩০ দিন পর আউটপুট ডিলিট হয়ে যাবে, যা স্টোরেজ বাঁচায় [citation:1]
- retention-days: 30
+ retention-days: 3
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 113f6c0..f1c7d6c 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -5,7 +5,7 @@ plugins {
}
android {
- namespace = "com.example.paygate"
+ namespace = "com.paygate.f"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -16,7 +16,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId = "com.example.paygate"
+ applicationId = "com.paygate.f"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6f8268b..0a69745 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -12,9 +12,10 @@
0)
- "✅ $forwarded টি নতুন SMS forward করা হয়েছে"
+ "✅ $forwarded new SMS(s) forwarded successfully"
else
- "SMS Forwarding চলছে... (প্রতি ১৫ সেকেন্ডে check)"
+ "SMS Forwarding is running... (checking every 15 seconds)"
updateNotification(notifText)
Log.d(TAG, "Poll done — forwarded: $forwarded")
diff --git a/android/app/src/main/kotlin/com/example/paygate/SmsReceiver.kt b/android/app/src/main/kotlin/com/paygate/f/SmsReceiver.kt
similarity index 99%
rename from android/app/src/main/kotlin/com/example/paygate/SmsReceiver.kt
rename to android/app/src/main/kotlin/com/paygate/f/SmsReceiver.kt
index f9d7c32..804ba2c 100644
--- a/android/app/src/main/kotlin/com/example/paygate/SmsReceiver.kt
+++ b/android/app/src/main/kotlin/com/paygate/f/SmsReceiver.kt
@@ -1,4 +1,4 @@
-package com.example.paygate
+package com.paygate.f
import android.content.BroadcastReceiver
import android.content.Context
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..93884e1 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..93884e1
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..b670b5b 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b670b5b
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..b22f102 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b22f102
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..db75b09 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db75b09
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..4435b8e 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4435b8e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/lib/main.dart b/lib/main.dart
index e960ca5..49653bc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -6,24 +6,24 @@ import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
-const MethodChannel _smsChannel = MethodChannel('com.example.paygate/sms');
+const MethodChannel _smsChannel = MethodChannel('com.paygate.f/sms');
// ─── Constants ────────────────────────────────────────────────────────────────
-const String kWorkerUrl = 'worker_url';
-const String kApiKey = 'api_key';
-const String kEnabled = 'sms_forward_enabled';
+const String kWorkerUrl = 'worker_url';
+const String kApiKey = 'api_key';
+const String kEnabled = 'sms_forward_enabled';
const String kForwardCount = 'forward_count';
-const String kLastLog = 'last_log';
-const String kSetupDone = 'setup_done';
-
+const String kLastLog = 'last_log';
+const String kSetupDone = 'setup_done';
+
+const Color kBg = Color(0xFF0A0A0A);
+const Color kSurface = Color(0xFF141414);
+const Color kBorder = Color(0xFF242424);
+const Color kMuted = Color(0xFF555555);
+const Color kText = Color(0xFFE8E8E8);
const Color kPrimary = Color(0xFFE2136E);
-const Color kBg = Color(0xFF0F0F0F);
-const Color kCard = Color(0xFF1A1A1A);
-const Color kBorder = Color(0xFF2A2A2A);
-const Color kMuted = Color(0xFF888888);
-const Color kSuccess = Color(0xFF22C55E);
-const Color kDanger = Color(0xFFEF4444);
-const Color kWarning = Color(0xFFF59E0B);
+const Color kGreen = Color(0xFF22C55E);
+const Color kRed = Color(0xFFEF4444);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -32,11 +32,9 @@ void main() async {
statusBarIconBrightness: Brightness.light,
));
final prefs = await SharedPreferences.getInstance();
- final setupDone = prefs.getBool(kSetupDone) ?? false;
- runApp(PayGateApp(setupDone: setupDone));
+ runApp(PayGateApp(setupDone: prefs.getBool(kSetupDone) ?? false));
}
-// ─── App Root ─────────────────────────────────────────────────────────────────
class PayGateApp extends StatelessWidget {
final bool setupDone;
const PayGateApp({super.key, required this.setupDone});
@@ -44,19 +42,97 @@ class PayGateApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
- title: 'PayGate SMS',
+ title: 'PayGate',
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.dark,
scaffoldBackgroundColor: kBg,
- fontFamily: 'sans-serif',
- colorScheme: const ColorScheme.dark(primary: kPrimary, surface: kCard),
+ colorScheme: const ColorScheme.dark(primary: kPrimary, surface: kSurface),
),
home: setupDone ? const HomeScreen() : const SetupScreen(),
);
}
}
+// ─── Shared Widgets ───────────────────────────────────────────────────────────
+class _Field extends StatelessWidget {
+ final TextEditingController ctrl;
+ final String label;
+ final String hint;
+ final bool obscure;
+ final TextInputType? keyboard;
+
+ const _Field({
+ required this.ctrl,
+ required this.label,
+ required this.hint,
+ this.obscure = false,
+ this.keyboard,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(label, style: const TextStyle(fontSize: 11, color: kMuted, letterSpacing: 0.8)),
+ const SizedBox(height: 6),
+ TextField(
+ controller: ctrl,
+ obscureText: obscure,
+ keyboardType: keyboard,
+ style: const TextStyle(color: kText, fontSize: 14),
+ decoration: InputDecoration(
+ hintText: hint,
+ hintStyle: const TextStyle(color: kMuted, fontSize: 13),
+ filled: true,
+ fillColor: kSurface,
+ contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: kBorder),
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: kBorder),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: kPrimary),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+Widget _btn(String label, VoidCallback? onTap, {bool outline = false, bool danger = false}) {
+ final color = danger ? kRed : kPrimary;
+ return SizedBox(
+ width: double.infinity,
+ height: 46,
+ child: outline
+ ? OutlinedButton(
+ onPressed: onTap,
+ style: OutlinedButton.styleFrom(
+ foregroundColor: color,
+ side: BorderSide(color: color),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ child: Text(label, style: const TextStyle(fontSize: 14)),
+ )
+ : ElevatedButton(
+ onPressed: onTap,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: color,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ child: Text(label, style: const TextStyle(fontSize: 14, color: Colors.white)),
+ ),
+ );
+}
+
// ─── Setup Screen ─────────────────────────────────────────────────────────────
class SetupScreen extends StatefulWidget {
const SetupScreen({super.key});
@@ -68,25 +144,21 @@ class _SetupScreenState extends State {
final _urlCtrl = TextEditingController();
final _keyCtrl = TextEditingController();
bool _testing = false;
- bool _saving = false;
- String _testResult = '';
- bool _testSuccess = false;
+ bool _saving = false;
+ String _testMsg = '';
+ bool _testOk = false;
@override
- void dispose() {
- _urlCtrl.dispose();
- _keyCtrl.dispose();
- super.dispose();
- }
+ void dispose() { _urlCtrl.dispose(); _keyCtrl.dispose(); super.dispose(); }
- Future _testConnection() async {
+ Future _test() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
if (url.isEmpty || key.isEmpty) {
- setState(() { _testResult = 'Worker URL এবং API Key দিন।'; _testSuccess = false; });
+ setState(() { _testMsg = 'Enter URL and API key first.'; _testOk = false; });
return;
}
- setState(() { _testing = true; _testResult = ''; });
+ setState(() { _testing = true; _testMsg = ''; });
try {
final res = await http.post(
Uri.parse('${url.trimRight()}/api/sms/forward'),
@@ -98,30 +170,29 @@ class _SetupScreenState extends State {
}),
).timeout(const Duration(seconds: 15));
- if (res.statusCode == 200 || res.statusCode == 201) {
- setState(() { _testResult = '✅ সংযোগ সফল! Worker সঠিকভাবে কাজ করছে।'; _testSuccess = true; });
- } else if (res.statusCode == 401) {
- setState(() { _testResult = '❌ API Key ভুল।'; _testSuccess = false; });
- } else {
- setState(() { _testResult = '⚠️ Server response: ${res.statusCode}'; _testSuccess = false; });
- }
+ final ok = res.statusCode >= 200 && res.statusCode < 300;
+ setState(() {
+ _testOk = ok;
+ _testMsg = ok ? 'Connection successful.' : 'HTTP ${res.statusCode}';
+ });
} on TimeoutException {
- setState(() { _testResult = '❌ Timeout। URL চেক করুন।'; _testSuccess = false; });
+ setState(() { _testMsg = 'Request timed out.'; _testOk = false; });
} catch (e) {
- setState(() { _testResult = '❌ Error: ${e.toString().substring(0, e.toString().length.clamp(0, 80))}'; _testSuccess = false; });
+ setState(() { _testMsg = 'Error: $e'; _testOk = false; });
}
setState(() => _testing = false);
}
- Future _saveAndContinue() async {
+ Future _save() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
- if (url.isEmpty) { _showSnack('Worker URL দিন'); return; }
- if (key.isEmpty) { _showSnack('API Key দিন'); return; }
- if (!url.startsWith('http')) { _showSnack('URL https:// দিয়ে শুরু হওয়া উচিত'); return; }
-
+ if (url.isEmpty) { _snack('Enter Worker URL'); return; }
+ if (key.isEmpty) { _snack('Enter API Key'); return; }
+ if (!url.startsWith('http')) { _snack('URL must start with https://'); return; }
setState(() => _saving = true);
- await _requestPermissions();
+
+ try { await _smsChannel.invokeMethod('requestSmsPermission'); } catch (_) {}
+ await Permission.notification.request();
final prefs = await SharedPreferences.getInstance();
await prefs.setString(kWorkerUrl, url);
@@ -129,190 +200,88 @@ class _SetupScreenState extends State {
await prefs.setBool(kEnabled, true);
await prefs.setBool(kSetupDone, true);
- // Foreground Service চালু করো
try { await _smsChannel.invokeMethod('startService'); } catch (_) {}
if (mounted) {
- Navigator.of(context).pushReplacement(
- MaterialPageRoute(builder: (_) => const HomeScreen()),
- );
+ Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeScreen()));
}
}
- Future _requestPermissions() async {
- try { await _smsChannel.invokeMethod('requestSmsPermission'); } catch (_) {}
- await Permission.notification.request();
- }
-
- void _showSnack(String msg) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(msg), backgroundColor: kDanger),
- );
- }
+ void _snack(String msg) => ScaffoldMessenger.of(context)
+ .showSnackBar(SnackBar(content: Text(msg), backgroundColor: kRed));
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
- padding: const EdgeInsets.all(24),
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- const SizedBox(height: 24),
- Row(
- children: [
- Container(
- width: 48, height: 48,
- decoration: BoxDecoration(color: kPrimary, borderRadius: BorderRadius.circular(12)),
- child: const Icon(Icons.bolt, color: Colors.white, size: 28),
- ),
- const SizedBox(width: 14),
- const Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('PayGate SMS', style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: Colors.white)),
- Text('bKash SMS Forwarder', style: TextStyle(fontSize: 13, color: kMuted)),
- ],
- ),
- ],
- ),
- const SizedBox(height: 32),
- _infoCard(
- icon: Icons.info_outline, color: kPrimary,
- title: 'প্রথমবার Setup',
- body: 'এই app আপনার bKash SMS গুলো স্বয়ংক্রিয়ভাবে আপনার Cloudflare Worker-এ forward করবে।',
+ // Logo
+ Container(
+ width: 40, height: 40,
+ decoration: BoxDecoration(color: kPrimary, borderRadius: BorderRadius.circular(10)),
+ child: const Icon(Icons.bolt, color: Colors.white, size: 22),
),
const SizedBox(height: 20),
- _label('Worker URL'),
- _textField(controller: _urlCtrl, hint: 'https://your-worker.workers.dev', icon: Icons.link, keyboard: TextInputType.url),
+ const Text('Setup', style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700, color: kText)),
+ const SizedBox(height: 6),
+ const Text('Connect your Cloudflare Worker to start forwarding bKash SMS.',
+ style: TextStyle(fontSize: 14, color: kMuted, height: 1.5)),
+ const SizedBox(height: 36),
+
+ _Field(ctrl: _urlCtrl, label: 'WORKER URL', hint: 'https://your-worker.workers.dev',
+ keyboard: TextInputType.url),
const SizedBox(height: 16),
- _label('API Key'),
- _textField(controller: _keyCtrl, hint: 'আপনার Worker এর API Key', icon: Icons.key, obscure: true),
- const SizedBox(height: 20),
- SizedBox(
- width: double.infinity,
- child: OutlinedButton.icon(
- onPressed: _testing ? null : _testConnection,
- icon: _testing
- ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: kPrimary))
- : const Icon(Icons.wifi_tethering, size: 18),
- label: Text(_testing ? 'Testing...' : 'Connection Test করুন'),
- style: OutlinedButton.styleFrom(
- foregroundColor: kPrimary, side: const BorderSide(color: kPrimary),
- padding: const EdgeInsets.symmetric(vertical: 14),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
- ),
- ),
- ),
- if (_testResult.isNotEmpty) ...[
- const SizedBox(height: 12),
+ _Field(ctrl: _keyCtrl, label: 'API KEY', hint: 'Paste your API key', obscure: true),
+ const SizedBox(height: 24),
+
+ // Test result
+ if (_testMsg.isNotEmpty) ...[
Container(
- padding: const EdgeInsets.all(12),
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
+ margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
- color: _testSuccess ? const Color(0xFF0F2D1A) : const Color(0xFF2D1212),
+ color: _testOk ? const Color(0xFF0D1F12) : const Color(0xFF1F0D0D),
borderRadius: BorderRadius.circular(8),
- border: Border.all(color: _testSuccess ? kSuccess : kDanger),
+ border: Border.all(color: _testOk ? const Color(0xFF1E4D2A) : const Color(0xFF4D1E1E)),
),
- child: Text(_testResult, style: TextStyle(fontSize: 13, color: _testSuccess ? kSuccess : const Color(0xFFFCA5A5))),
+ child: Text(_testMsg,
+ style: TextStyle(fontSize: 13, color: _testOk ? kGreen : kRed)),
),
],
- const SizedBox(height: 16),
- SizedBox(
- width: double.infinity,
- child: ElevatedButton(
- onPressed: _saving ? null : _saveAndContinue,
- style: ElevatedButton.styleFrom(
- backgroundColor: kPrimary,
- padding: const EdgeInsets.symmetric(vertical: 16),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
- ),
- child: _saving
- ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
- : const Text('Save করুন ও শুরু করুন →', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white)),
- ),
- ),
- const SizedBox(height: 32),
- _sectionTitle('কীভাবে কাজ করে?'),
- const SizedBox(height: 12),
- _stepCard('১', 'bKash থেকে SMS আসলে instantly forward হয়'),
- _stepCard('২', 'Background এ প্রতি ১৫ সেকেন্ডে SMS inbox check করে'),
- _stepCard('৩', 'Notification bar এ সবসময় status দেখা যায়'),
- _stepCard('৪', 'Phone restart হলেও স্বয়ংক্রিয়ভাবে চালু হয়'),
- ],
- ),
- ),
- ),
- );
- }
- Widget _label(String text) => Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: Text(text, style: const TextStyle(fontSize: 13, color: kMuted, fontWeight: FontWeight.w500)),
- );
+ _btn(_testing ? 'Testing…' : 'Test Connection', _testing ? null : _test, outline: true),
+ const SizedBox(height: 10),
+ _btn(_saving ? 'Saving…' : 'Save & Continue', _saving ? null : _save),
- Widget _textField({
- required TextEditingController controller,
- required String hint,
- required IconData icon,
- TextInputType? keyboard,
- bool obscure = false,
- }) {
- return TextField(
- controller: controller,
- keyboardType: keyboard,
- obscureText: obscure,
- style: const TextStyle(color: Colors.white, fontSize: 14),
- decoration: InputDecoration(
- hintText: hint,
- hintStyle: const TextStyle(color: kMuted, fontSize: 13),
- prefixIcon: Icon(icon, color: kMuted, size: 20),
- filled: true, fillColor: const Color(0xFF111111),
- border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: kBorder)),
- enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: kBorder)),
- focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: kPrimary)),
- contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
- ),
- );
- }
+ const SizedBox(height: 40),
+ const Divider(color: kBorder),
+ const SizedBox(height: 20),
- Widget _infoCard({required IconData icon, required Color color, required String title, required String body}) {
- return Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(color: kCard, borderRadius: BorderRadius.circular(12), border: Border.all(color: kBorder)),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Icon(icon, color: color, size: 22),
- const SizedBox(width: 12),
- Expanded(child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(title, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14, color: Colors.white)),
- const SizedBox(height: 4),
- Text(body, style: const TextStyle(fontSize: 13, color: kMuted, height: 1.6)),
+ const Text('How it works', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: kText)),
+ const SizedBox(height: 12),
+ ...[
+ 'Forwards bKash SMS instantly when received',
+ 'Polls SMS inbox every 15 seconds as backup',
+ 'Runs in background, visible in notification bar',
+ 'Restarts automatically after phone reboot',
+ ].map((s) => Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('— ', style: TextStyle(color: kMuted, fontSize: 13)),
+ Expanded(child: Text(s, style: const TextStyle(color: kMuted, fontSize: 13, height: 1.4))),
+ ],
+ ),
+ )),
],
- )),
- ],
- ),
- );
- }
-
- Widget _sectionTitle(String text) => Text(text, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white));
-
- Widget _stepCard(String num, String text) {
- return Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: Row(
- children: [
- Container(
- width: 28, height: 28,
- decoration: BoxDecoration(color: kPrimary, borderRadius: BorderRadius.circular(14)),
- child: Center(child: Text(num, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: Colors.white))),
),
- const SizedBox(width: 12),
- Expanded(child: Text(text, style: const TextStyle(fontSize: 13, color: kMuted, height: 1.5))),
- ],
+ ),
),
);
}
@@ -326,72 +295,68 @@ class HomeScreen extends StatefulWidget {
}
class _HomeScreenState extends State with WidgetsBindingObserver {
- String _workerUrl = '';
- String _apiKey = '';
- bool _enabled = true;
- int _forwardCount = 0;
- String _lastLog = '';
- bool _hasPerms = false;
- Timer? _refreshTimer;
+ String _workerUrl = '';
+ String _apiKey = '';
+ bool _enabled = true;
+ int _forwardCount = 0;
+ String _lastLog = '';
+ bool _hasPerms = false;
+ Timer? _timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
- _loadPrefs();
- _checkPermissions();
- _refreshTimer = Timer.periodic(const Duration(seconds: 3), (_) => _loadPrefs());
- // Service চালু করো
+ _load();
+ _checkPerms();
+ _timer = Timer.periodic(const Duration(seconds: 3), (_) => _load());
_startService();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
- _refreshTimer?.cancel();
+ _timer?.cancel();
super.dispose();
}
@override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) {
- // App background এ গেলে service নিশ্চিত করো
- _startService();
- }
+ void didChangeAppLifecycleState(AppLifecycleState s) {
+ if (s == AppLifecycleState.paused || s == AppLifecycleState.detached) _startService();
}
Future _startService() async {
try { await _smsChannel.invokeMethod('startService'); } catch (_) {}
}
- Future _loadPrefs() async {
- final prefs = await SharedPreferences.getInstance();
+ Future _load() async {
+ final p = await SharedPreferences.getInstance();
if (!mounted) return;
setState(() {
- _workerUrl = prefs.getString(kWorkerUrl) ?? '';
- _apiKey = prefs.getString(kApiKey) ?? '';
- _enabled = prefs.getBool(kEnabled) ?? true;
- _forwardCount = prefs.getInt(kForwardCount) ?? 0;
- _lastLog = prefs.getString(kLastLog) ?? 'কোনো activity নেই';
+ _workerUrl = p.getString(kWorkerUrl) ?? '';
+ _apiKey = p.getString(kApiKey) ?? '';
+ _enabled = p.getBool(kEnabled) ?? true;
+ _forwardCount = p.getInt(kForwardCount) ?? 0;
+ _lastLog = p.getString(kLastLog) ?? '—';
});
}
- Future _checkPermissions() async {
+ Future _checkPerms() async {
try {
- final granted = await _smsChannel.invokeMethod('hasSmsPermission') ?? false;
- if (mounted) setState(() => _hasPerms = granted);
+ final ok = await _smsChannel.invokeMethod('hasSmsPermission') ?? false;
+ if (mounted) setState(() => _hasPerms = ok);
} catch (_) {
- final sms = await Permission.sms.status;
- if (mounted) setState(() => _hasPerms = sms.isGranted);
+ final s = await Permission.sms.status;
+ if (mounted) setState(() => _hasPerms = s.isGranted);
}
}
Future _toggleEnabled() async {
- final prefs = await SharedPreferences.getInstance();
- final newVal = !_enabled;
- await prefs.setBool(kEnabled, newVal);
- setState(() => _enabled = newVal);
- if (newVal) {
+ final p = await SharedPreferences.getInstance();
+ final next = !_enabled;
+ await p.setBool(kEnabled, next);
+ setState(() => _enabled = next);
+ if (next) {
_startService();
} else {
try { await _smsChannel.invokeMethod('stopService'); } catch (_) {}
@@ -404,41 +369,39 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
}
await Permission.notification.request();
await Future.delayed(const Duration(seconds: 1));
- await _checkPermissions();
- }
-
- void _openSettings() {
- Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen()))
- .then((_) => _loadPrefs());
+ await _checkPerms();
}
@override
Widget build(BuildContext context) {
+ final active = _enabled && _hasPerms;
+
return Scaffold(
body: SafeArea(
child: Column(
children: [
// Top bar
- Container(
+ Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
- decoration: const BoxDecoration(
- color: Color(0xFF111111),
- border: Border(bottom: BorderSide(color: kBorder)),
- ),
child: Row(
children: [
Container(
- width: 36, height: 36,
+ width: 32, height: 32,
decoration: BoxDecoration(color: kPrimary, borderRadius: BorderRadius.circular(8)),
- child: const Icon(Icons.bolt, color: Colors.white, size: 20),
+ child: const Icon(Icons.bolt, color: Colors.white, size: 18),
),
const SizedBox(width: 10),
- const Text('PayGate SMS', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white)),
+ const Text('PayGate', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: kText)),
const Spacer(),
- IconButton(onPressed: _openSettings, icon: const Icon(Icons.settings, color: kMuted)),
+ GestureDetector(
+ onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen()))
+ .then((_) => _load()),
+ child: const Icon(Icons.settings_outlined, color: kMuted, size: 22),
+ ),
],
),
),
+ const Divider(height: 1, color: kBorder),
Expanded(
child: SingleChildScrollView(
@@ -446,64 +409,55 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+
// Permission warning
if (!_hasPerms) ...[
- _alertCard(
- color: kDanger, bgColor: const Color(0xFF2D1212),
- icon: Icons.warning_amber,
- title: 'SMS Permission নেই!',
- body: 'SMS forward করতে permission দিতে হবে।',
- action: TextButton(
- onPressed: _requestPerms,
- child: const Text('Permission দিন', style: TextStyle(color: kDanger, fontWeight: FontWeight.w600)),
+ _notice(
+ 'SMS permission required.',
+ action: GestureDetector(
+ onTap: _requestPerms,
+ child: const Text('Grant →', style: TextStyle(color: kRed, fontSize: 13, fontWeight: FontWeight.w600)),
),
+ color: kRed,
),
const SizedBox(height: 16),
],
- // Status card
+ // Status row
Container(
- padding: const EdgeInsets.all(20),
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: _enabled && _hasPerms
- ? [const Color(0xFF0F2D1A), const Color(0xFF1A4A2A)]
- : [const Color(0xFF1A1A1A), const Color(0xFF222222)],
- begin: Alignment.topLeft, end: Alignment.bottomRight,
- ),
- borderRadius: BorderRadius.circular(14),
- border: Border.all(color: _enabled && _hasPerms ? kSuccess.withValues(alpha: 0.4) : kBorder),
+ color: kSurface,
+ borderRadius: BorderRadius.circular(10),
+ border: Border.all(color: kBorder),
),
child: Row(
children: [
Container(
- width: 52, height: 52,
+ width: 8, height: 8,
decoration: BoxDecoration(
- color: (_enabled && _hasPerms ? kSuccess : kMuted).withValues(alpha: 0.15),
- borderRadius: BorderRadius.circular(26),
- ),
- child: Icon(
- _enabled && _hasPerms ? Icons.sms : Icons.sms_failed,
- color: _enabled && _hasPerms ? kSuccess : kMuted, size: 26,
+ color: active ? kGreen : kMuted,
+ shape: BoxShape.circle,
),
),
- const SizedBox(width: 14),
+ const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- _enabled && _hasPerms ? 'SMS Forwarding Active' : 'SMS Forwarding Inactive',
+ active ? 'Forwarding active' : 'Forwarding inactive',
style: TextStyle(
- fontSize: 15, fontWeight: FontWeight.w700,
- color: _enabled && _hasPerms ? kSuccess : kMuted,
+ fontSize: 14,
+ fontWeight: FontWeight.w600,
+ color: active ? kGreen : kMuted,
),
),
const SizedBox(height: 2),
Text(
- _enabled && _hasPerms
- ? 'Notification চলছে • প্রতি ১৫ সেকেন্ডে check'
- : _hasPerms ? 'Toggle করে চালু করুন' : 'SMS permission দিন',
+ active
+ ? 'Running · checks every 15s'
+ : _hasPerms ? 'Toggle to enable' : 'SMS permission needed',
style: const TextStyle(fontSize: 12, color: kMuted),
),
],
@@ -512,96 +466,67 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
Switch(
value: _enabled,
onChanged: (_) => _toggleEnabled(),
- activeThumbColor: kSuccess,
- activeTrackColor: kSuccess.withValues(alpha: 0.3),
+ activeThumbColor: kGreen,
+ activeTrackColor: const Color(0xFF0D2A14),
+ inactiveThumbColor: kMuted,
+ inactiveTrackColor: kSurface,
),
],
),
),
- const SizedBox(height: 16),
+ const SizedBox(height: 12),
- // Stats
+ // Stats row
Row(
children: [
- Expanded(child: _statCard('Forwarded', '$_forwardCount', Icons.send, kPrimary)),
- const SizedBox(width: 12),
- Expanded(child: _statCard('Permission', _hasPerms ? 'Granted' : 'Denied',
- _hasPerms ? Icons.check_circle : Icons.cancel, _hasPerms ? kSuccess : kDanger)),
- const SizedBox(width: 12),
- Expanded(child: _statCard('Service', _enabled ? 'Running' : 'Stopped',
- _enabled ? Icons.circle : Icons.pause_circle, _enabled ? kSuccess : kWarning)),
+ _stat('Forwarded', '$_forwardCount'),
+ const SizedBox(width: 10),
+ _stat('Permission', _hasPerms ? 'Granted' : 'Denied'),
+ const SizedBox(width: 10),
+ _stat('Service', _enabled ? 'On' : 'Off'),
],
),
- const SizedBox(height: 16),
-
- // Service info card
- Container(
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(
- color: const Color(0xFF0F1E2D),
- borderRadius: BorderRadius.circular(10),
- border: Border.all(color: const Color(0xFF3B82F6).withValues(alpha: 0.3)),
- ),
- child: Row(
- children: [
- const Icon(Icons.notifications_active, color: Color(0xFF60A5FA), size: 20),
- const SizedBox(width: 10),
- const Expanded(
- child: Text(
- 'Background Service চলছে। Notification bar এ সবসময় দেখা যাবে। প্রতি ১৫ সেকেন্ডে SMS inbox check করে।',
- style: TextStyle(fontSize: 12, color: Color(0xFF93C5FD), height: 1.5),
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 16),
+ const SizedBox(height: 20),
- // Last log
- _sectionTitle('Last Activity'),
+ // Last activity
+ const Text('Last activity', style: TextStyle(fontSize: 11, color: kMuted, letterSpacing: 0.8)),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
- color: const Color(0xFF111111),
- borderRadius: BorderRadius.circular(10),
+ color: kSurface,
+ borderRadius: BorderRadius.circular(8),
border: Border.all(color: kBorder),
),
child: Text(_lastLog,
- style: const TextStyle(fontSize: 13, color: kMuted, fontFamily: 'monospace', height: 1.5)),
+ style: const TextStyle(fontSize: 12, color: kMuted, fontFamily: 'monospace', height: 1.5)),
),
- const SizedBox(height: 16),
+ const SizedBox(height: 20),
// Config
- _sectionTitle('Current Config'),
+ const Text('Configuration', style: TextStyle(fontSize: 11, color: kMuted, letterSpacing: 0.8)),
const SizedBox(height: 8),
Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(color: kCard, borderRadius: BorderRadius.circular(12), border: Border.all(color: kBorder)),
+ padding: const EdgeInsets.all(14),
+ decoration: BoxDecoration(
+ color: kSurface,
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: kBorder),
+ ),
child: Column(
children: [
- _configRow('Worker URL', _workerUrl.isEmpty ? 'Not set' : _workerUrl),
- const Divider(color: kBorder, height: 20),
- _configRow('API Key', _apiKey.isEmpty ? 'Not set' : '${_apiKey.substring(0, _apiKey.length.clamp(0, 6))}••••••'),
+ _cfgRow('URL', _workerUrl.isEmpty ? 'Not set' : _workerUrl),
+ const SizedBox(height: 10),
+ const Divider(color: kBorder, height: 1),
+ const SizedBox(height: 10),
+ _cfgRow('Key',
+ _apiKey.isEmpty
+ ? 'Not set'
+ : '${_apiKey.substring(0, _apiKey.length.clamp(0, 6))}••••••'),
],
),
),
- const SizedBox(height: 24),
-
- SizedBox(
- width: double.infinity,
- child: OutlinedButton.icon(
- onPressed: _openSettings,
- icon: const Icon(Icons.tune, size: 18),
- label: const Text('Settings খুলুন'),
- style: OutlinedButton.styleFrom(
- foregroundColor: kPrimary, side: const BorderSide(color: kPrimary),
- padding: const EdgeInsets.symmetric(vertical: 14),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
- ),
- ),
- ),
],
),
),
@@ -612,49 +537,50 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
);
}
- Widget _sectionTitle(String t) => Text(t, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: Colors.white));
-
- Widget _statCard(String label, String value, IconData icon, Color color) {
- return Container(
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(color: kCard, borderRadius: BorderRadius.circular(10), border: Border.all(color: kBorder)),
- child: Column(children: [
- Icon(icon, color: color, size: 22),
- const SizedBox(height: 6),
- Text(value, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: color)),
- const SizedBox(height: 2),
- Text(label, style: const TextStyle(fontSize: 11, color: kMuted)),
- ]),
+ Widget _stat(String label, String val) {
+ return Expanded(
+ child: Container(
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ decoration: BoxDecoration(color: kSurface, borderRadius: BorderRadius.circular(8), border: Border.all(color: kBorder)),
+ child: Column(
+ children: [
+ Text(val, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: kText)),
+ const SizedBox(height: 2),
+ Text(label, style: const TextStyle(fontSize: 11, color: kMuted)),
+ ],
+ ),
+ ),
);
}
- Widget _configRow(String label, String value) {
+ Widget _cfgRow(String label, String val) {
return Row(
- crossAxisAlignment: CrossAxisAlignment.start,
children: [
- SizedBox(width: 90, child: Text(label, style: const TextStyle(fontSize: 13, color: kMuted))),
- Expanded(child: Text(value, style: const TextStyle(fontSize: 13, color: Colors.white), overflow: TextOverflow.ellipsis, maxLines: 2)),
+ SizedBox(width: 36, child: Text(label, style: const TextStyle(fontSize: 12, color: kMuted))),
+ const SizedBox(width: 10),
+ Expanded(
+ child: Text(val,
+ style: const TextStyle(fontSize: 12, color: kText),
+ overflow: TextOverflow.ellipsis, maxLines: 1),
+ ),
],
);
}
- Widget _alertCard({required Color color, required Color bgColor, required IconData icon, required String title, required String body, Widget? action}) {
+ Widget _notice(String msg, {Widget? action, required Color color}) {
return Container(
- padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withValues(alpha: 0.5))),
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
+ decoration: BoxDecoration(
+ color: kSurface,
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: color.withValues(alpha: 0.4)),
+ ),
child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Icon(icon, color: color, size: 20),
+ Icon(Icons.info_outline, color: color, size: 16),
const SizedBox(width: 10),
- Expanded(child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(title, style: TextStyle(fontWeight: FontWeight.w600, color: color, fontSize: 13)),
- Text(body, style: const TextStyle(fontSize: 12, color: kMuted)),
- ?action,
- ],
- )),
+ Expanded(child: Text(msg, style: TextStyle(fontSize: 13, color: color))),
+ ?action,
],
),
);
@@ -671,79 +597,86 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State {
final _urlCtrl = TextEditingController();
final _keyCtrl = TextEditingController();
- bool _saved = false;
- bool _testing = false;
- String _testResult = '';
- bool _testSuccess = false;
+ bool _saved = false;
+ bool _testing = false;
+ String _testMsg = '';
+ bool _testOk = false;
@override
- void initState() { super.initState(); _loadPrefs(); }
+ void initState() { super.initState(); _load(); }
@override
void dispose() { _urlCtrl.dispose(); _keyCtrl.dispose(); super.dispose(); }
- Future _loadPrefs() async {
- final prefs = await SharedPreferences.getInstance();
- _urlCtrl.text = prefs.getString(kWorkerUrl) ?? '';
- _keyCtrl.text = prefs.getString(kApiKey) ?? '';
+ Future _load() async {
+ final p = await SharedPreferences.getInstance();
+ _urlCtrl.text = p.getString(kWorkerUrl) ?? '';
+ _keyCtrl.text = p.getString(kApiKey) ?? '';
}
Future _save() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
if (url.isEmpty || key.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('URL এবং API Key দিন'), backgroundColor: kDanger));
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Enter URL and API Key'), backgroundColor: kRed));
return;
}
- final prefs = await SharedPreferences.getInstance();
- await prefs.setString(kWorkerUrl, url);
- await prefs.setString(kApiKey, key);
+ final p = await SharedPreferences.getInstance();
+ await p.setString(kWorkerUrl, url);
+ await p.setString(kApiKey, key);
setState(() => _saved = true);
Future.delayed(const Duration(seconds: 2), () { if (mounted) setState(() => _saved = false); });
}
- Future _testConnection() async {
+ Future _test() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
- if (url.isEmpty || key.isEmpty) { setState(() { _testResult = 'URL এবং API Key দিন।'; _testSuccess = false; }); return; }
- setState(() { _testing = true; _testResult = ''; });
+ if (url.isEmpty || key.isEmpty) {
+ setState(() { _testMsg = 'Enter URL and API key first.'; _testOk = false; });
+ return;
+ }
+ setState(() { _testing = true; _testMsg = ''; });
try {
final res = await http.post(
Uri.parse('${url.trimRight()}/api/sms/forward'),
headers: {'Content-Type': 'application/json', 'X-API-Key': key},
- body: jsonEncode({'sender': '01700000000', 'message': 'You have received Tk 10.00 from 01700000000. TrxID TESTCONN01. Balance Tk 100.00.', 'receivedAt': DateTime.now().toUtc().toIso8601String()}),
+ body: jsonEncode({
+ 'sender': '01700000000',
+ 'message': 'You have received Tk 10.00 from 01700000000. TrxID TESTCONN01. Balance Tk 100.00.',
+ 'receivedAt': DateTime.now().toUtc().toIso8601String(),
+ }),
).timeout(const Duration(seconds: 15));
- setState(() {
- _testSuccess = res.statusCode >= 200 && res.statusCode < 300;
- _testResult = _testSuccess ? '✅ সংযোগ সফল! (HTTP ${res.statusCode})' : '❌ HTTP ${res.statusCode}';
- });
+ final ok = res.statusCode >= 200 && res.statusCode < 300;
+ setState(() { _testOk = ok; _testMsg = ok ? 'Connection successful.' : 'HTTP ${res.statusCode}'; });
} catch (e) {
- setState(() { _testResult = '❌ Error: ${e.toString().substring(0, e.toString().length.clamp(0, 80))}'; _testSuccess = false; });
+ setState(() { _testMsg = 'Error: $e'; _testOk = false; });
}
setState(() => _testing = false);
}
- Future _resetApp() async {
- final confirm = await showDialog(
+ Future _reset() async {
+ final ok = await showDialog(
context: context,
builder: (ctx) => AlertDialog(
- backgroundColor: kCard,
- title: const Text('Reset করবেন?', style: TextStyle(color: Colors.white)),
- content: const Text('সব settings মুছে setup screen-এ যাবে।', style: TextStyle(color: kMuted)),
+ backgroundColor: kSurface,
+ title: const Text('Reset app?', style: TextStyle(color: kText, fontSize: 16)),
+ content: const Text('All settings will be cleared.', style: TextStyle(color: kMuted, fontSize: 14)),
actions: [
- TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('না')),
- TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('হ্যাঁ, Reset', style: TextStyle(color: kDanger))),
+ TextButton(onPressed: () => Navigator.pop(ctx, false),
+ child: const Text('Cancel', style: TextStyle(color: kMuted))),
+ TextButton(onPressed: () => Navigator.pop(ctx, true),
+ child: const Text('Reset', style: TextStyle(color: kRed))),
],
),
);
- if (confirm == true) {
+ if (ok == true) {
try { await _smsChannel.invokeMethod('stopService'); } catch (_) {}
- final prefs = await SharedPreferences.getInstance();
- await prefs.clear();
+ final p = await SharedPreferences.getInstance();
+ await p.clear();
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
- MaterialPageRoute(builder: (_) => const SetupScreen()), (_) => false,
- );
+ MaterialPageRoute(builder: (_) => const SetupScreen()), (_) => false);
}
}
}
@@ -752,9 +685,13 @@ class _SettingsScreenState extends State {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- backgroundColor: const Color(0xFF111111),
- title: const Text('Settings', style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.w600)),
- leading: IconButton(icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context)),
+ backgroundColor: kBg,
+ elevation: 0,
+ leading: IconButton(
+ icon: const Icon(Icons.arrow_back, color: kText, size: 20),
+ onPressed: () => Navigator.pop(context),
+ ),
+ title: const Text('Settings', style: TextStyle(color: kText, fontSize: 15, fontWeight: FontWeight.w600)),
bottom: const PreferredSize(preferredSize: Size.fromHeight(1), child: Divider(height: 1, color: kBorder)),
),
body: SingleChildScrollView(
@@ -762,92 +699,84 @@ class _SettingsScreenState extends State {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (_saved)
+
+ if (_saved) ...[
Container(
- width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16),
- decoration: BoxDecoration(color: const Color(0xFF0F2D1A), borderRadius: BorderRadius.circular(8), border: Border.all(color: kSuccess.withValues(alpha: 0.5))),
- child: const Text('✅ Settings save হয়েছে!', style: TextStyle(color: kSuccess, fontSize: 13)),
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
+ margin: const EdgeInsets.only(bottom: 16),
+ decoration: BoxDecoration(
+ color: const Color(0xFF0D1F12),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: const Color(0xFF1E4D2A)),
+ ),
+ child: const Text('Settings saved.', style: TextStyle(color: kGreen, fontSize: 13)),
),
- const Text('Worker Configuration', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: Colors.white)),
- const SizedBox(height: 14),
- Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(color: kCard, borderRadius: BorderRadius.circular(12), border: Border.all(color: kBorder)),
- child: Column(children: [
- _settingsField('Worker URL', _urlCtrl, 'https://your-worker.workers.dev', Icons.link),
- const SizedBox(height: 14),
- _settingsField('API Key', _keyCtrl, 'আপনার API Key', Icons.key, obscure: true),
- ]),
- ),
+ ],
+
+ _Field(ctrl: _urlCtrl, label: 'WORKER URL', hint: 'https://your-worker.workers.dev',
+ keyboard: TextInputType.url),
const SizedBox(height: 14),
+ _Field(ctrl: _keyCtrl, label: 'API KEY', hint: 'Paste your API key', obscure: true),
+ const SizedBox(height: 20),
+
+ if (_testMsg.isNotEmpty) ...[
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
+ margin: const EdgeInsets.only(bottom: 14),
+ decoration: BoxDecoration(
+ color: _testOk ? const Color(0xFF0D1F12) : const Color(0xFF1F0D0D),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(color: _testOk ? const Color(0xFF1E4D2A) : const Color(0xFF4D1E1E)),
+ ),
+ child: Text(_testMsg, style: TextStyle(fontSize: 13, color: _testOk ? kGreen : kRed)),
+ ),
+ ],
+
Row(
children: [
- Expanded(
- child: OutlinedButton.icon(
- onPressed: _testing ? null : _testConnection,
- icon: _testing ? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: kPrimary)) : const Icon(Icons.wifi_tethering, size: 16),
- label: const Text('Test'),
- style: OutlinedButton.styleFrom(foregroundColor: kPrimary, side: const BorderSide(color: kPrimary), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
+ Expanded(child: SizedBox(
+ height: 46,
+ child: OutlinedButton(
+ onPressed: _testing ? null : _test,
+ style: OutlinedButton.styleFrom(
+ foregroundColor: kPrimary,
+ side: const BorderSide(color: kBorder),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ child: Text(_testing ? 'Testing…' : 'Test', style: const TextStyle(fontSize: 14)),
),
- ),
+ )),
const SizedBox(width: 10),
- Expanded(
- child: ElevatedButton.icon(
+ Expanded(child: SizedBox(
+ height: 46,
+ child: ElevatedButton(
onPressed: _save,
- icon: const Icon(Icons.save, size: 16, color: Colors.white),
- label: const Text('Save', style: TextStyle(color: Colors.white)),
- style: ElevatedButton.styleFrom(backgroundColor: kPrimary, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: kPrimary,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ child: const Text('Save', style: TextStyle(fontSize: 14, color: Colors.white)),
),
- ),
+ )),
],
),
- if (_testResult.isNotEmpty) ...[
- const SizedBox(height: 10),
- Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(color: _testSuccess ? const Color(0xFF0F2D1A) : const Color(0xFF2D1212), borderRadius: BorderRadius.circular(8), border: Border.all(color: (_testSuccess ? kSuccess : kDanger).withValues(alpha: 0.5))),
- child: Text(_testResult, style: TextStyle(fontSize: 12, color: _testSuccess ? kSuccess : const Color(0xFFFCA5A5))),
- ),
- ],
- const SizedBox(height: 24),
+
+ const SizedBox(height: 32),
const Divider(color: kBorder),
- const SizedBox(height: 16),
- SizedBox(
- width: double.infinity,
- child: OutlinedButton.icon(
- onPressed: _resetApp,
- icon: const Icon(Icons.restore, size: 16),
- label: const Text('App Reset করুন'),
- style: OutlinedButton.styleFrom(foregroundColor: kDanger, side: const BorderSide(color: kDanger), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
- ),
- ),
const SizedBox(height: 20),
- const Center(child: Text('PayGate SMS Forwarder v1.0.0', style: TextStyle(fontSize: 11, color: kMuted))),
+
+ _btn('Reset App', _reset, outline: true, danger: true),
+
+ const SizedBox(height: 24),
+ const Center(
+ child: Text('PayGate SMS Forwarder v1.0.1',
+ style: TextStyle(fontSize: 11, color: kMuted)),
+ ),
],
),
),
);
}
-
- Widget _settingsField(String label, TextEditingController ctrl, String hint, IconData icon, {bool obscure = false}) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(label, style: const TextStyle(fontSize: 12, color: kMuted, fontWeight: FontWeight.w500)),
- const SizedBox(height: 6),
- TextField(
- controller: ctrl, obscureText: obscure,
- style: const TextStyle(color: Colors.white, fontSize: 13),
- decoration: InputDecoration(
- hintText: hint, hintStyle: const TextStyle(color: kMuted, fontSize: 12),
- prefixIcon: Icon(icon, color: kMuted, size: 18), filled: true, fillColor: const Color(0xFF111111),
- border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: kBorder)),
- enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: kBorder)),
- focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: kPrimary)),
- contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
- ),
- ),
- ],
- );
- }
}
diff --git a/lib/main.dart.bak b/lib/main.dart.bak
index 70e0888..9037159 100644
--- a/lib/main.dart.bak
+++ b/lib/main.dart.bak
@@ -83,7 +83,7 @@ class _SetupScreenState extends State {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
if (url.isEmpty || key.isEmpty) {
- setState(() { _testResult = 'Worker URL এবং API Key দিন।'; _testSuccess = false; });
+ setState(() { _testResult = 'Please enter Worker URL and API Key.'; _testSuccess = false; });
return;
}
setState(() { _testing = true; _testResult = ''; });
@@ -99,14 +99,14 @@ class _SetupScreenState extends State {
).timeout(const Duration(seconds: 15));
if (res.statusCode == 200 || res.statusCode == 201) {
- setState(() { _testResult = '✅ সংযোগ সফল! Worker সঠিকভাবে কাজ করছে।'; _testSuccess = true; });
+ setState(() { _testResult = '✅ Connection successful! Worker is running correctly.'; _testSuccess = true; });
} else if (res.statusCode == 401) {
- setState(() { _testResult = '❌ API Key ভুল।'; _testSuccess = false; });
+ setState(() { _testResult = '❌ Invalid API Key.'; _testSuccess = false; });
} else {
setState(() { _testResult = '⚠️ Server response: ${res.statusCode}'; _testSuccess = false; });
}
} on TimeoutException {
- setState(() { _testResult = '❌ Timeout। URL চেক করুন।'; _testSuccess = false; });
+ setState(() { _testResult = '❌ Timeout. Please check the URL.'; _testSuccess = false; });
} catch (e) {
setState(() { _testResult = '❌ Error: ${e.toString().substring(0, e.toString().length.clamp(0, 80))}'; _testSuccess = false; });
}
@@ -116,9 +116,9 @@ class _SetupScreenState extends State {
Future _saveAndContinue() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
- if (url.isEmpty) { _showSnack('Worker URL দিন'); return; }
- if (key.isEmpty) { _showSnack('API Key দিন'); return; }
- if (!url.startsWith('http')) { _showSnack('URL https:// দিয়ে শুরু হওয়া উচিত'); return; }
+ if (url.isEmpty) { _showSnack('Please enter Worker URL'); return; }
+ if (key.isEmpty) { _showSnack('Please enter API Key'); return; }
+ if (!url.startsWith('http')) { _showSnack('URL must start with https://'); return; }
setState(() => _saving = true);
await _requestPermissions();
@@ -129,7 +129,6 @@ class _SetupScreenState extends State {
await prefs.setBool(kEnabled, true);
await prefs.setBool(kSetupDone, true);
- // Foreground Service চালু করো
try { await _smsChannel.invokeMethod('startService'); } catch (_) {}
if (mounted) {
@@ -180,15 +179,15 @@ class _SetupScreenState extends State {
const SizedBox(height: 32),
_infoCard(
icon: Icons.info_outline, color: kPrimary,
- title: 'প্রথমবার Setup',
- body: 'এই app আপনার bKash SMS গুলো স্বয়ংক্রিয়ভাবে আপনার Cloudflare Worker-এ forward করবে।',
+ title: 'Initial Setup',
+ body: 'This app will automatically forward your bKash SMS messages to your Cloudflare Worker.',
),
const SizedBox(height: 20),
_label('Worker URL'),
_textField(controller: _urlCtrl, hint: 'https://your-worker.workers.dev', icon: Icons.link, keyboard: TextInputType.url),
const SizedBox(height: 16),
_label('API Key'),
- _textField(controller: _keyCtrl, hint: 'আপনার Worker এর API Key', icon: Icons.key, obscure: true),
+ _textField(controller: _keyCtrl, hint: 'Your Worker API Key', icon: Icons.key, obscure: true),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
@@ -197,7 +196,7 @@ class _SetupScreenState extends State {
icon: _testing
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: kPrimary))
: const Icon(Icons.wifi_tethering, size: 18),
- label: Text(_testing ? 'Testing...' : 'Connection Test করুন'),
+ label: Text(_testing ? 'Testing...' : 'Test Connection'),
style: OutlinedButton.styleFrom(
foregroundColor: kPrimary, side: const BorderSide(color: kPrimary),
padding: const EdgeInsets.symmetric(vertical: 14),
@@ -229,16 +228,16 @@ class _SetupScreenState extends State {
),
child: _saving
? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
- : const Text('Save করুন ও শুরু করুন →', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white)),
+ : const Text('Save & Get Started →', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white)),
),
),
const SizedBox(height: 32),
- _sectionTitle('কীভাবে কাজ করে?'),
+ _sectionTitle('How It Works'),
const SizedBox(height: 12),
- _stepCard('১', 'bKash থেকে SMS আসলে instantly forward হয়'),
- _stepCard('২', 'Background এ প্রতি ১৫ সেকেন্ডে SMS inbox check করে'),
- _stepCard('৩', 'Notification bar এ সবসময় status দেখা যায়'),
- _stepCard('৪', 'Phone restart হলেও স্বয়ংক্রিয়ভাবে চালু হয়'),
+ _stepCard('1', 'Instantly forwards SMS as soon as it arrives from bKash'),
+ _stepCard('2', 'Checks SMS inbox every 15 seconds in the background'),
+ _stepCard('3', 'Status is always visible in the notification bar'),
+ _stepCard('4', 'Automatically restarts after phone reboot'),
],
),
),
@@ -341,7 +340,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
_loadPrefs();
_checkPermissions();
_refreshTimer = Timer.periodic(const Duration(seconds: 3), (_) => _loadPrefs());
- // Service চালু করো
_startService();
}
@@ -355,7 +353,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) {
- // App background এ গেলে service নিশ্চিত করো
_startService();
}
}
@@ -372,7 +369,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
_apiKey = prefs.getString(kApiKey) ?? '';
_enabled = prefs.getBool(kEnabled) ?? true;
_forwardCount = prefs.getInt(kForwardCount) ?? 0;
- _lastLog = prefs.getString(kLastLog) ?? 'কোনো activity নেই';
+ _lastLog = prefs.getString(kLastLog) ?? 'No activity yet';
});
}
@@ -451,11 +448,11 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
_alertCard(
color: kDanger, bgColor: const Color(0xFF2D1212),
icon: Icons.warning_amber,
- title: 'SMS Permission নেই!',
- body: 'SMS forward করতে permission দিতে হবে।',
+ title: 'SMS Permission Required!',
+ body: 'Permission is required to forward SMS messages.',
action: TextButton(
onPressed: _requestPerms,
- child: const Text('Permission দিন', style: TextStyle(color: kDanger, fontWeight: FontWeight.w600)),
+ child: const Text('Grant Permission', style: TextStyle(color: kDanger, fontWeight: FontWeight.w600)),
),
),
const SizedBox(height: 16),
@@ -472,14 +469,14 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
begin: Alignment.topLeft, end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(14),
- border: Border.all(color: _enabled && _hasPerms ? kSuccess.withOpacity(0.4) : kBorder),
+ border: Border.all(color: _enabled && _hasPerms ? kSuccess.withValues(alpha: 0.4) : kBorder),
),
child: Row(
children: [
Container(
width: 52, height: 52,
decoration: BoxDecoration(
- color: (_enabled && _hasPerms ? kSuccess : kMuted).withOpacity(0.15),
+ color: (_enabled && _hasPerms ? kSuccess : kMuted).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(26),
),
child: Icon(
@@ -502,8 +499,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
const SizedBox(height: 2),
Text(
_enabled && _hasPerms
- ? 'Notification চলছে • প্রতি ১৫ সেকেন্ডে check'
- : _hasPerms ? 'Toggle করে চালু করুন' : 'SMS permission দিন',
+ ? 'Notification running • Checks every 15 seconds'
+ : _hasPerms ? 'Toggle to enable' : 'Grant SMS permission',
style: const TextStyle(fontSize: 12, color: kMuted),
),
],
@@ -512,8 +509,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
Switch(
value: _enabled,
onChanged: (_) => _toggleEnabled(),
- activeColor: kSuccess,
- activeTrackColor: kSuccess.withOpacity(0.3),
+ activeThumbColor: kSuccess,
+ activeTrackColor: kSuccess.withValues(alpha: 0.3),
),
],
),
@@ -540,15 +537,15 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
decoration: BoxDecoration(
color: const Color(0xFF0F1E2D),
borderRadius: BorderRadius.circular(10),
- border: Border.all(color: const Color(0xFF3B82F6).withOpacity(0.3)),
+ border: Border.all(color: const Color(0xFF3B82F6).withValues(alpha: 0.3)),
),
- child: Row(
+ child: const Row(
children: [
- const Icon(Icons.notifications_active, color: Color(0xFF60A5FA), size: 20),
- const SizedBox(width: 10),
- const Expanded(
+ Icon(Icons.notifications_active, color: Color(0xFF60A5FA), size: 20),
+ SizedBox(width: 10),
+ Expanded(
child: Text(
- 'Background Service চলছে। Notification bar এ সবসময় দেখা যাবে। প্রতি ১৫ সেকেন্ডে SMS inbox check করে।',
+ 'Background service is running. Always visible in the notification bar. Checks SMS inbox every 15 seconds.',
style: TextStyle(fontSize: 12, color: Color(0xFF93C5FD), height: 1.5),
),
),
@@ -594,7 +591,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
child: OutlinedButton.icon(
onPressed: _openSettings,
icon: const Icon(Icons.tune, size: 18),
- label: const Text('Settings খুলুন'),
+ label: const Text('Open Settings'),
style: OutlinedButton.styleFrom(
foregroundColor: kPrimary, side: const BorderSide(color: kPrimary),
padding: const EdgeInsets.symmetric(vertical: 14),
@@ -641,7 +638,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
Widget _alertCard({required Color color, required Color bgColor, required IconData icon, required String title, required String body, Widget? action}) {
return Container(
padding: const EdgeInsets.all(14),
- decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withOpacity(0.5))),
+ decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withValues(alpha: 0.5))),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -652,7 +649,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
children: [
Text(title, style: TextStyle(fontWeight: FontWeight.w600, color: color, fontSize: 13)),
Text(body, style: const TextStyle(fontSize: 12, color: kMuted)),
- if (action != null) action,
+ ?action,
],
)),
],
@@ -692,7 +689,7 @@ class _SettingsScreenState extends State {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
if (url.isEmpty || key.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('URL এবং API Key দিন'), backgroundColor: kDanger));
+ ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please enter URL and API Key'), backgroundColor: kDanger));
return;
}
final prefs = await SharedPreferences.getInstance();
@@ -705,7 +702,7 @@ class _SettingsScreenState extends State {
Future _testConnection() async {
final url = _urlCtrl.text.trim();
final key = _keyCtrl.text.trim();
- if (url.isEmpty || key.isEmpty) { setState(() { _testResult = 'URL এবং API Key দিন।'; _testSuccess = false; }); return; }
+ if (url.isEmpty || key.isEmpty) { setState(() { _testResult = 'Please enter URL and API Key.'; _testSuccess = false; }); return; }
setState(() { _testing = true; _testResult = ''; });
try {
final res = await http.post(
@@ -715,7 +712,7 @@ class _SettingsScreenState extends State {
).timeout(const Duration(seconds: 15));
setState(() {
_testSuccess = res.statusCode >= 200 && res.statusCode < 300;
- _testResult = _testSuccess ? '✅ সংযোগ সফল! (HTTP ${res.statusCode})' : '❌ HTTP ${res.statusCode}';
+ _testResult = _testSuccess ? '✅ Connection successful! (HTTP ${res.statusCode})' : '❌ HTTP ${res.statusCode}';
});
} catch (e) {
setState(() { _testResult = '❌ Error: ${e.toString().substring(0, e.toString().length.clamp(0, 80))}'; _testSuccess = false; });
@@ -728,11 +725,11 @@ class _SettingsScreenState extends State {
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: kCard,
- title: const Text('Reset করবেন?', style: TextStyle(color: Colors.white)),
- content: const Text('সব settings মুছে setup screen-এ যাবে।', style: TextStyle(color: kMuted)),
+ title: const Text('Reset App?', style: TextStyle(color: Colors.white)),
+ content: const Text('All settings will be cleared and you will be taken to the setup screen.', style: TextStyle(color: kMuted)),
actions: [
- TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('না')),
- TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('হ্যাঁ, Reset', style: TextStyle(color: kDanger))),
+ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
+ TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Yes, Reset', style: TextStyle(color: kDanger))),
],
),
);
@@ -765,8 +762,8 @@ class _SettingsScreenState extends State {
if (_saved)
Container(
width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16),
- decoration: BoxDecoration(color: const Color(0xFF0F2D1A), borderRadius: BorderRadius.circular(8), border: Border.all(color: kSuccess.withOpacity(0.5))),
- child: const Text('✅ Settings save হয়েছে!', style: TextStyle(color: kSuccess, fontSize: 13)),
+ decoration: BoxDecoration(color: const Color(0xFF0F2D1A), borderRadius: BorderRadius.circular(8), border: Border.all(color: kSuccess.withValues(alpha: 0.5))),
+ child: const Text('✅ Settings saved successfully!', style: TextStyle(color: kSuccess, fontSize: 13)),
),
const Text('Worker Configuration', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: Colors.white)),
const SizedBox(height: 14),
@@ -776,7 +773,7 @@ class _SettingsScreenState extends State {
child: Column(children: [
_settingsField('Worker URL', _urlCtrl, 'https://your-worker.workers.dev', Icons.link),
const SizedBox(height: 14),
- _settingsField('API Key', _keyCtrl, 'আপনার API Key', Icons.key, obscure: true),
+ _settingsField('API Key', _keyCtrl, 'Your API Key', Icons.key, obscure: true),
]),
),
const SizedBox(height: 14),
@@ -805,7 +802,7 @@ class _SettingsScreenState extends State {
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(color: _testSuccess ? const Color(0xFF0F2D1A) : const Color(0xFF2D1212), borderRadius: BorderRadius.circular(8), border: Border.all(color: (_testSuccess ? kSuccess : kDanger).withOpacity(0.5))),
+ decoration: BoxDecoration(color: _testSuccess ? const Color(0xFF0F2D1A) : const Color(0xFF2D1212), borderRadius: BorderRadius.circular(8), border: Border.all(color: (_testSuccess ? kSuccess : kDanger).withValues(alpha: 0.5))),
child: Text(_testResult, style: TextStyle(fontSize: 12, color: _testSuccess ? kSuccess : const Color(0xFFFCA5A5))),
),
],
@@ -817,7 +814,7 @@ class _SettingsScreenState extends State {
child: OutlinedButton.icon(
onPressed: _resetApp,
icon: const Icon(Icons.restore, size: 16),
- label: const Text('App Reset করুন'),
+ label: const Text('Reset App'),
style: OutlinedButton.styleFrom(foregroundColor: kDanger, side: const BorderSide(color: kDanger), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
),
),
diff --git a/pubspec.yaml b/pubspec.yaml
index c533685..77f64cb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: paygate
description: "PayGate SMS Forwarder - bKash SMS auto-forward to Cloudflare Worker"
publish_to: 'none'
-version: 1.0.0+1
+version: 1.0.1+2
environment:
sdk: ^3.12.0
diff --git a/test/widget_test.dart b/test/widget_test.dart
index 13fb415..7ee0706 100644
--- a/test/widget_test.dart
+++ b/test/widget_test.dart
@@ -5,6 +5,6 @@ import 'package:paygate/main.dart';
void main() {
testWidgets('SetupScreen renders correctly', (WidgetTester tester) async {
await tester.pumpWidget(const PayGateApp(setupDone: false));
- expect(find.text('PayGate SMS'), findsWidgets);
+ expect(find.text('Setup'), findsOneWidget);
});
}
diff --git a/test/widget_test.dart.bak b/test/widget_test.dart.bak
index 636744b..13fb415 100644
--- a/test/widget_test.dart.bak
+++ b/test/widget_test.dart.bak
@@ -1,30 +1,10 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility in the flutter_test package. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:paygate/main.dart';
void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
- // Build our app and trigger a frame.
- await tester.pumpWidget(const MyApp());
-
- // Verify that our counter starts at 0.
- expect(find.text('0'), findsOneWidget);
- expect(find.text('1'), findsNothing);
-
- // Tap the '+' icon and trigger a frame.
- await tester.tap(find.byIcon(Icons.add));
- await tester.pump();
-
- // Verify that our counter has incremented.
- expect(find.text('0'), findsNothing);
- expect(find.text('1'), findsOneWidget);
+ testWidgets('SetupScreen renders correctly', (WidgetTester tester) async {
+ await tester.pumpWidget(const PayGateApp(setupDone: false));
+ expect(find.text('PayGate SMS'), findsWidgets);
});
}