Added ads

This commit is contained in:
Matic Ivešić 2026-02-07 14:23:33 +01:00
parent eed999d2ab
commit 7c47298cd7
13 changed files with 258 additions and 29 deletions

View File

@ -1,3 +1,4 @@
{
"cmake.ignoreCMakeListsMissing": true
"cmake.ignoreCMakeListsMissing": true,
"java.configuration.updateBuildConfiguration": "disabled"
}

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<application
android:label="QuickSSH"
@ -33,6 +34,9 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-2626773788355001~4397436505"/>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and

View File

@ -1,5 +1,6 @@
package com.example.quick_ssh
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterActivity()
class MainActivity : FlutterFragmentActivity(){
}

View File

@ -1,18 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<style name="NormalTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -45,5 +45,8 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSFaceIDUsageDescription</key>
<string>QuickSSH needs FaceID to protect your terminal access.</string>
</dict>
</plist>

View File

@ -7,7 +7,13 @@ import 'package:QuickSSH/services/theme_controller.dart';
final themeController = ThemeController();
final settingsController = SettingsController();
void main() {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await settingsController.initializationFuture;
runApp(MyApp());
}
@ -22,6 +28,8 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Quick SSH',
navigatorKey: navigatorKey,
theme: MyThemes.lightTheme,
darkTheme: MyThemes.darkTheme,
themeMode: themeController.themeMode,

View File

@ -5,6 +5,9 @@ import 'add_client.dart';
import 'package:QuickSSH/classes/ServerCommand.dart';
import 'package:QuickSSH/services/storage_service.dart';
import 'package:QuickSSH/screens/settings.dart';
import 'package:flutter/services.dart';
import 'package:QuickSSH/main.dart';
import 'package:QuickSSH/widgets/ad_banner.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@ -20,6 +23,13 @@ class _HomeScreenState extends State<HomeScreen> {
void initState() {
super.initState();
_loadData();
WidgetsBinding.instance.addPostFrameCallback((_) async {
bool allowed = await settingsController.checkSecurity();
if (!allowed) {
SystemNavigator.pop();
}
});
}
void _loadData() async {
@ -106,6 +116,9 @@ class _HomeScreenState extends State<HomeScreen> {
);
},
),
bottomNavigationBar: SafeArea(
child: MyBannerAd(isEnabled: settingsController.adsEnabled),
),
backgroundColor: theme.surface,
);
}
@ -151,7 +164,9 @@ class _HomeScreenState extends State<HomeScreen> {
_openSettings() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Settings()),
);
MaterialPageRoute(builder: (context) => Settings()),
).then((_) {
setState(() {});
});
}
}

View File

@ -115,6 +115,64 @@ class _SettingsState extends State<Settings> {
),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(
child: Text(
"Biometric Unlock",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: theme.onSurface,
),
),
),
Expanded(
child: Switch(
activeTrackColor: theme.primary,
activeThumbColor: theme.onPrimary,
inactiveThumbColor: theme.onSurface,
inactiveTrackColor: theme.surface,
value: settingsController.biometricEnabled,
onChanged: (bool value) {
settingsController.toggleBiometric(value);
if (value) HapticFeedback.mediumImpact();
setState(() {});
},
),
),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(
child: Text(
"Enable Ads",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: theme.onSurface,
),
),
),
Expanded(
child: Switch(
activeTrackColor: theme.primary,
activeThumbColor: theme.onPrimary,
inactiveThumbColor: theme.onSurface,
inactiveTrackColor: theme.surface,
value: settingsController.adsEnabled,
onChanged: (bool value) {
settingsController.toggleAds(value);
if (value) HapticFeedback.mediumImpact();
setState(() {});
},
),
),
],
),
],
),
),

View File

@ -1,25 +1,71 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:local_auth/local_auth.dart';
class SettingsController extends ChangeNotifier {
bool _vibrationEnabled = true;
bool get vibrationEnabled => _vibrationEnabled;
bool _biometricEnabled = false;
bool get biometricEnabled => _biometricEnabled;
bool _adsEnabled = true;
bool get adsEnabled => _adsEnabled;
final LocalAuthentication auth = LocalAuthentication();
late Future<void> initializationFuture;
SettingsController() {
_loadSettings();
initializationFuture = _loadSettings();
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
_vibrationEnabled = prefs.getBool('vibration_enabled') ?? true;
_biometricEnabled = prefs.getBool('biometric_enabled') ?? false;
_adsEnabled = prefs.getBool('ads_enabled') ?? true;
notifyListeners();
}
Future<void> toggleVibration(bool value) async {
print("Toggling vibration to: $value");
_vibrationEnabled = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('vibration_enabled', value);
notifyListeners();
}
Future<void> toggleBiometric(bool value) async {
_biometricEnabled = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('biometric_enabled', value);
notifyListeners();
}
Future<void> toggleAds(bool value) async {
_adsEnabled = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('ads_enabled', value);
notifyListeners();
}
Future<bool> checkSecurity() async {
await initializationFuture;
if (!_biometricEnabled) return true;
bool canCheck = await auth.canCheckBiometrics;
bool isSupported = await auth.isDeviceSupported();
if (!canCheck && !isSupported) return true;
try {
return await auth.authenticate(
localizedReason: 'Unlock QuickSSH',
biometricOnly: false,
);
} catch (e) {
return false;
}
}
}

View File

@ -0,0 +1,58 @@
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
class MyBannerAd extends StatefulWidget {
final bool isEnabled;
const MyBannerAd({super.key, required this.isEnabled});
@override
State<MyBannerAd> createState() => _MyBannerAdState();
}
class _MyBannerAdState extends State<MyBannerAd> {
BannerAd? _bannerAd;
bool _isLoaded = false;
bool _isInitializing = false;
@override
void initState() {
super.initState();
if (widget.isEnabled) {
_loadAd();
}
}
void _loadAd() async {
_bannerAd = BannerAd(
adUnitId: 'ca-app-pub-2626773788355001/7557216229',
request: const AdRequest(),
size: AdSize.banner,
listener: BannerAdListener(
onAdLoaded: (ad) => setState(() => _isLoaded = true),
onAdFailedToLoad: (ad, error) {
ad.dispose();
print('Ad failed to load: $error');
},
),
)..load();
}
@override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!widget.isEnabled || !_isLoaded || _bannerAd == null) {
return const SizedBox.shrink();
}
return Container(
alignment: Alignment.center,
width: double.infinity,
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
);
}
}

View File

@ -9,10 +9,12 @@ import flutter_secure_storage_macos
import local_auth_darwin
import path_provider_foundation
import shared_preferences_foundation
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
}

View File

@ -224,6 +224,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
google_mobile_ads:
dependency: "direct main"
description:
name: google_mobile_ads
sha256: "0d4a3744b5e8ed1b8be6a1b452d309f811688855a497c6113fc4400f922db603"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
image:
dependency: transitive
description:
@ -292,26 +300,26 @@ packages:
dependency: "direct main"
description:
name: local_auth
sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b"
sha256: a4f1bf57f0236a4aeb5e8f0ec180e197f4b112a3456baa6c1e73b546630b0422
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "3.0.0"
local_auth_android:
dependency: transitive
description:
name: local_auth_android
sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467
sha256: "162b8e177fd9978c4620da2a8002a5c6bed4d20f0c6daf5137e72e9a8b767d2e"
url: "https://pub.dev"
source: hosted
version: "1.0.56"
version: "2.0.4"
local_auth_darwin:
dependency: transitive
description:
name: local_auth_darwin
sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
sha256: "668ea65edaab17380956e9713f57e34f78ede505ca0cfd8d39db34e2f260bfee"
url: "https://pub.dev"
source: hosted
version: "1.6.1"
version: "2.0.1"
local_auth_platform_interface:
dependency: transitive
description:
@ -324,10 +332,10 @@ packages:
dependency: transitive
description:
name: local_auth_windows
sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5
sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16
url: "https://pub.dev"
source: hosted
version: "1.0.11"
version: "2.0.1"
matcher:
dependency: transitive
description:
@ -597,6 +605,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9
url: "https://pub.dev"
source: hosted
version: "4.13.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510
url: "https://pub.dev"
source: hosted
version: "4.10.11"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
url: "https://pub.dev"
source: hosted
version: "2.14.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "0412b657a2828fb301e73509909e6ec02b77cd2b441ae9f77125d482b3ddf0e7"
url: "https://pub.dev"
source: hosted
version: "3.23.6"
win32:
dependency: transitive
description:

View File

@ -33,7 +33,8 @@ dependencies:
shared_preferences: ^2.5.0
dartssh2: ^2.0.0
flutter_secure_storage: ^9.2.2
local_auth: ^2.3.0
local_auth: ^3.0.0
google_mobile_ads: ^5.1.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.