Skip to content
Open

Sui #10

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
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"files.associations": {
"variant": "cpp"
},
"cmake.sourceDirectory": "D:/Work/pay_evm/linux"
}
8 changes: 5 additions & 3 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ plugins {

android {
namespace = "site.kanari.kanaripay"
compileSdk = flutter.compileSdkVersion
// Use explicit SDK versions to ensure newer Android attributes (eg. allowTaskSnapshot) are available
compileSdk = 36
ndkVersion = flutter.ndkVersion

compileOptions {
Expand All @@ -24,8 +25,9 @@ android {
applicationId = "site.kanari.kanaripay"
// 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
targetSdk = flutter.targetSdkVersion
minSdk = flutter.minSdkVersion
// Explicit targetSdk to match compileSdk and enable newer platform attributes
targetSdk = 36
versionCode = flutter.versionCode
versionName = flutter.versionName
}
Expand Down
14 changes: 14 additions & 0 deletions android/app/src/main/kotlin/site/kanari/kanaripay/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
package site.kanari.kanaripay

import android.os.Bundle
import android.view.WindowManager
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Prevent screenshots and screen recording for this activity
// This sets the FLAG_SECURE window flag which tells the system
// to treat the content of the window as secure, preventing it from
// appearing in screenshots or on non-secure displays.
window?.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
}
74 changes: 74 additions & 0 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,85 @@ import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
var privacyOverlay: UIView?

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

// Observe for screen capture (screenshot & recording) state changes
NotificationCenter.default.addObserver(
self,
selector: #selector(screenCaptureChanged),
name: UIScreen.capturedDidChangeNotification,
object: nil
)

// Observe for user screenshots (note: iOS does not allow blocking screenshots,
// but we can detect them and immediately cover sensitive content)
NotificationCenter.default.addObserver(
self,
selector: #selector(userDidTakeScreenshot),
name: UIApplication.userDidTakeScreenshotNotification,
object: nil
)

// Initial check
if UIScreen.main.isCaptured {
showPrivacyOverlay()
}

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

@objc func screenCaptureChanged() {
DispatchQueue.main.async {
if UIScreen.main.isCaptured {
self.showPrivacyOverlay()
} else {
self.hidePrivacyOverlay()
}
}
}

@objc func userDidTakeScreenshot() {
// Show the privacy overlay briefly when a screenshot is taken
showPrivacyOverlay()
// Hide after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.hidePrivacyOverlay()
}
}

func showPrivacyOverlay() {
guard let window = UIApplication.shared.windows.first else { return }
if privacyOverlay != nil { return }

let overlay = UIView(frame: window.bounds)
overlay.backgroundColor = UIColor.black
overlay.alpha = 0.95
overlay.isUserInteractionEnabled = false

// Optional: Add a label explaining why the screen is hidden
let label = UILabel()
label.text = "Screen capture is disabled"
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
overlay.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
label.centerYAnchor.constraint(equalTo: overlay.centerYAnchor)
])

window.addSubview(overlay)
privacyOverlay = overlay
}

func hidePrivacyOverlay() {
privacyOverlay?.removeFromSuperview()
privacyOverlay = nil
}
}
68 changes: 19 additions & 49 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:kanaripay/screenpage/WelcomeScreen.dart';
import 'package:kanaripay/screenpage/WalletScreen.dart';
Expand All @@ -9,6 +10,9 @@ import 'package:kanaripay/services/security_service.dart';
import 'package:kanaripay/providers/theme_provider.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
// Enter immersive fullscreen mode (hides status bar)
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
runApp(const MyApp());
}

Expand Down Expand Up @@ -121,75 +125,41 @@ class _AppInitializerState extends State<AppInitializer>

@override
Widget build(BuildContext context) {
// Ensure AppTheme dynamic colors are in sync with the active Theme
AppTheme.updateThemeColors(Theme.of(context).brightness == Brightness.dark);

if (_isLoading) {
final bool isDark = Theme.of(context).brightness == Brightness.dark;

return Scaffold(
backgroundColor: AppTheme.surfaceColor,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// App Logo with pulse animation
TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 1500),
tween: Tween(begin: 0.8, end: 1.0),
tween: Tween(begin: 0.86, end: 1.0),
builder: (context, scale, child) {
return Transform.scale(
scale: scale,
child: Container(
width: 120,
height: 120,
width: 110,
height: 110,
decoration: BoxDecoration(
color: isDark
? AppTheme.primaryColor.withOpacity(0.15)
: AppTheme.primaryColor.withOpacity(0.18),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(
isDark ? 0.2 : 0.08,
),
blurRadius: 30,
offset: const Offset(0, 15),
),
],
),
child: Icon(
Icons.account_balance_wallet,
size: 60,
color: isDark ? Colors.white : AppTheme.primaryColor,
color: isDark ? Theme.of(context).cardColor : Theme.of(context).colorScheme.primary.withOpacity(0.06),
borderRadius: BorderRadius.circular(26),
boxShadow: AppTheme.elevatedShadow,
),
child: Icon(Icons.account_balance_wallet, size: 54, color: Theme.of(context).colorScheme.primary),
),
);
},
),
const SizedBox(height: 32),
Text(
'Kanari Wallet',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: isDark
? Colors.white
: Theme.of(context).textTheme.titleLarge?.color ??
AppTheme.textPrimary,
letterSpacing: 1.2,
),
),
const SizedBox(height: 48),
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(
isDark
? AppTheme.primaryColor.withOpacity(0.95)
: AppTheme.primaryColor,
),
),
),
const SizedBox(height: 20),
Text('Kanari Wallet', style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 36),
SizedBox(width: 44, height: 44, child: CircularProgressIndicator(strokeWidth: 3, valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary))),
],
),
),
Expand Down
2 changes: 1 addition & 1 deletion lib/screenpage/AddCustomNetworkScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ class _AddCustomNetworkScreenState extends State<AddCustomNetworkScreen> {
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
radius: 12,
backgroundColor: Colors.transparent,
backgroundColor: AppTheme.transparent,
child: ClipOval(
child: Image.network(
_iconUrlController.text.trim(),
Expand Down
2 changes: 1 addition & 1 deletion lib/screenpage/AddTokenScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class _AddTokenScreenState extends State<AddTokenScreen>
],
),
elevation: 0,
backgroundColor: Colors.transparent,
backgroundColor: AppTheme.transparent,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
Expand Down
21 changes: 12 additions & 9 deletions lib/screenpage/CreateWalletScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ class _CreateWalletScreenState extends State<CreateWalletScreen>
const Text('Create Wallet'),
],
),
elevation: 0,
backgroundColor: Colors.transparent,
elevation: 0,
backgroundColor: AppTheme.transparent,
),
body: FadeTransition(
opacity: _fadeAnimation,
Expand All @@ -280,9 +280,9 @@ class _CreateWalletScreenState extends State<CreateWalletScreen>
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
// Use theme-aware background so it looks correct in dark mode
color: Theme.of(context).brightness == Brightness.dark
? Theme.of(context).cardColor
: Colors.white,
color: Theme.of(context).brightness == Brightness.dark
? Theme.of(context).cardColor
: AppTheme.cardColor,
borderRadius: BorderRadius.circular(AppTheme.borderRadiusLarge),
boxShadow: AppTheme.elevatedShadow,
),
Expand All @@ -302,9 +302,9 @@ class _CreateWalletScreenState extends State<CreateWalletScreen>
],
),
indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent,
// Keep selected label visible (white text on colored indicator)
labelColor: Colors.white,
dividerColor: AppTheme.transparent,
// Keep selected label visible (use theme's onPrimary so it's correct in all modes)
labelColor: Theme.of(context).colorScheme.onPrimary,
// Use theme text color for unselected labels so they remain readable
unselectedLabelColor:
Theme.of(context).textTheme.bodySmall?.color ?? AppTheme.textSecondary,
Expand Down Expand Up @@ -410,7 +410,10 @@ class _CreateWalletScreenState extends State<CreateWalletScreen>
children: [
Row(
children: [
Icon(Icons.info, color: Theme.of(context).primaryColor),
Icon(
Icons.info,
color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor,
),
const SizedBox(width: 8),
Text(
_isSui ? 'Create New Sui Wallet' : 'Create New Wallet',
Expand Down
2 changes: 1 addition & 1 deletion lib/screenpage/MarkdownPage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class _MarkdownPageState extends State<MarkdownPage> {
backgroundColor: AppTheme.surfaceColor,
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.transparent,
backgroundColor: AppTheme.transparent,
elevation: 0,
),
body: _isLoading
Expand Down
8 changes: 4 additions & 4 deletions lib/screenpage/NetworkSelectionScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class _NetworkSelectionScreenState extends State<NetworkSelectionScreen> {
),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
style: TextButton.styleFrom(foregroundColor: AppTheme.errorColor),
child: const Text('Delete'),
),
],
Expand Down Expand Up @@ -152,7 +152,7 @@ class _NetworkSelectionScreenState extends State<NetworkSelectionScreen> {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
elevation: isActive ? 4 : 1,
color: isActive ? Theme.of(context).primaryColor.withOpacity(0.1) : null,
color: isActive ? (Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor).withOpacity(0.1) : null,
child: ListTile(
leading: _buildNetworkAvatar(network),
title: Text(
Expand Down Expand Up @@ -188,7 +188,7 @@ class _NetworkSelectionScreenState extends State<NetworkSelectionScreen> {
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isActive) const Icon(Icons.check_circle, color: Colors.green),
if (isActive) const Icon(Icons.check_circle, color: AppTheme.successColor),
if (showMenu)
PopupMenuButton<String>(
onSelected: (value) {
Expand Down Expand Up @@ -327,7 +327,7 @@ class _NetworkSelectionScreenState extends State<NetworkSelectionScreen> {
children: [const Text('Select Network')],
),
elevation: 0,
backgroundColor: Colors.transparent,
backgroundColor: AppTheme.transparent,
),
body: _isLoading
? Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(AppTheme.primaryColor)))
Expand Down
12 changes: 9 additions & 3 deletions lib/screenpage/PinSetupScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import 'package:flutter/services.dart';
import '../utils/app_theme.dart';

class PinSetupScreen extends StatefulWidget {
final Function(String) onPinSetup;
// Expect an async callback so callers can perform async setup (e.g. store PIN)
final Future<void> Function(String) onPinSetup;

const PinSetupScreen({
super.key,
Expand Down Expand Up @@ -119,9 +120,14 @@ class _PinSetupScreenState extends State<PinSetupScreen> with TickerProviderStat
} else {
if (pin == _firstPin) {
setState(() => _isLoading = true);

try {
widget.onPinSetup(pin);
// Await caller's async setup so we can update loading state correctly.
await widget.onPinSetup(pin);
if (mounted) {
setState(() {
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
Expand Down
Loading