diff --git a/.gitignore b/.gitignore index 972083d..5f174f8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ .dart_tool/ .packages build/ +release/ +*.apk +*.aab # If you're building an application, you may want to check-in your pubspec.lock pubspec.lock @@ -28,12 +31,8 @@ doc/api/ lib/firebase_options.dart *.exe android/app/google-services.json -.idea/caches/deviceStreaming.xml -.idea/caches/deviceStreaming.xml -.idea/deviceManager.xml +.idea/caches/ firebase.json .idea/deviceManager.xml -android/.kotlin/sessions/kotlin-compiler-11069125661880765564.salive -.idea/caches/deviceStreaming.xml -.idea/deviceManager.xml +android/.kotlin/ /.tmp.driveupload diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9c09a3d..38d1a64 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -49,17 +49,23 @@ android { signingConfigs { create("release") { - keyAlias = keystoreProperties["keyAlias"] as String - keyPassword = keystoreProperties["keyPassword"] as String - storeFile = keystoreProperties["storeFile"]?.let { file(it) } - storePassword = keystoreProperties["storePassword"] as String + if (keystorePropertiesFile.exists()) { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = keystoreProperties["storeFile"]?.let { file(it) } + storePassword = keystoreProperties["storePassword"] as String + } } } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("release") + signingConfig = if (keystorePropertiesFile.exists()) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } } debug { signingConfig = signingConfigs.getByName("debug") diff --git a/assets/additional_resources.json b/assets/additional_resources.json index 76c3292..eb6e02c 100644 --- a/assets/additional_resources.json +++ b/assets/additional_resources.json @@ -33,7 +33,7 @@ "type": "link", "collection": "appdocs", "name": "Application's Google Store page", - "value": "https://play.google.com/store/apps/details?id=page.puzzak.geminilocal" + "value": "https://play.google.com/store/apps/details?id=page.puzzak.paios" }, { "type": "link", @@ -41,4 +41,4 @@ "name": "Developer's Website", "value": "https://puzzak.page" } -] \ No newline at end of file +] diff --git a/lib/engine.dart b/lib/engine.dart index ea68e34..41a3c34 100644 --- a/lib/engine.dart +++ b/lib/engine.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; @@ -636,7 +635,6 @@ class AIEngine with md.ChangeNotifier { status = "Sending prompt..."; notifyListeners(); - final int runTokens = chatData.chats.containsKey(currentChat) && chatData.chats[currentChat].containsKey("chatTokens") ? chatData.chats[currentChat]["chatTokens"] : tokens; double runTemperature = chatData.chats.containsKey(currentChat) && chatData.chats[currentChat].containsKey("chatTemperature") ? chatData.chats[currentChat]["chatTemperature"] : temperature; final stream = gemini.generateTextEvents( diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index 45aa493..082a1a8 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -12,8 +12,8 @@ class ChatPage extends StatefulWidget { } class ChatPageState extends State { - text tWid = text(); - @override + final TextBlocks textBlocks = TextBlocks(); + @override void initState() { super.initState(); @@ -104,7 +104,7 @@ class ChatPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( - children: tWid.chatlog( + children: textBlocks.chatlog( conversation: [ { "user": "User", @@ -136,7 +136,7 @@ class ChatPageState extends State { lastUser: "" ), ), - text.infoShort( + TextBlocks.infoShort( title: engine.dict.value("welcome"), context: context, subtitle: "", @@ -146,7 +146,7 @@ class ChatPageState extends State { ) : Column( children: [ - ...tWid.chatlog( + ...textBlocks.chatlog( conversation: engine.context, context: context, aiChunk: engine.responseText, diff --git a/lib/pages/chats.dart b/lib/pages/chats.dart index 72a0d39..0e020f1 100644 --- a/lib/pages/chats.dart +++ b/lib/pages/chats.dart @@ -273,7 +273,7 @@ class ChatsPageState extends State { }).toList().whereNot((crd) => crd == null).cast(), ] ), - text.info( + TextBlocks.info( title: engine.isLoading?engine.dict.value("still_generating"):engine.dict.value(engine.chats.isEmpty?"no_chats":"chats_desc").replaceAll("%chatnum%", engine.chats.length.toString()), subtitle: engine.chats.isEmpty?engine.dict.value("new_chat"):"", action: engine.chats.isNotEmpty?(){}:engine.isLoading?(){}:(){ @@ -303,4 +303,4 @@ class ChatsPageState extends State { ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/intro.dart b/lib/pages/intro.dart index 8dc6368..43a06a7 100644 --- a/lib/pages/intro.dart +++ b/lib/pages/intro.dart @@ -220,7 +220,7 @@ class IntroPageState extends State { ]), if(engine.modelDownloadLog.isNotEmpty) if(engine.modelDownloadLog[engine.modelDownloadLog.length-1]["info"] == "downloading_model") - text.infoShort( + TextBlocks.infoShort( title: engine.dict.value("welcome_gtfo"), subtitle: "", action: () {}, @@ -257,9 +257,11 @@ class IntroPageState extends State { title: language["origin"], subtitle: language["name"] == language["origin"] ? "" : language["name"], action: () async { + final navigator = Navigator.of(dialogContext); await engine.dict.saveLanguage(language["id"]); + if (!mounted) return; setState(() {}); - Navigator.of(dialogContext).pop(); + navigator.pop(); } ); }).toList().cast() @@ -303,7 +305,7 @@ class IntroPageState extends State { SizedBox(height: 20,), if(engine.modelDownloadLog.isNotEmpty) if(engine.modelDownloadLog[engine.modelDownloadLog.length-1]["status"] == "Download") - text.info( + TextBlocks.info( title: engine.dict.value("welcome_download").replaceAll("%size%", convertSize(engine.usualModelSize, false)), subtitle: "", action: () {}, @@ -312,7 +314,7 @@ class IntroPageState extends State { if(engine.modelDownloadLog.isNotEmpty) if(!(engine.modelDownloadLog[engine.modelDownloadLog.length-1]["status"] == "Download")) - text.info( + TextBlocks.info( title: engine.modelInfo["version"] == null ? engine.dict.value("welcome_unavailable") : engine.modelInfo["version"] == "Unknown" @@ -343,4 +345,4 @@ class IntroPageState extends State { ); }); } -} \ No newline at end of file +} diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index f65aa73..a059c11 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -4,10 +4,8 @@ import 'package:geminilocal/pages/settings/prompts.dart'; import 'package:geminilocal/pages/settings/resources.dart'; import 'package:geminilocal/storage/file_access_service.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../engine.dart'; import 'support/elements.dart'; -import 'package:intl/intl.dart'; import 'package:geminilocal/pages/settings/model.dart'; @@ -91,9 +89,11 @@ class SettingsPageState extends State { title: language["origin"], subtitle: language["name"] == language["origin"] ? "" : language["name"], action: () async { + final navigator = Navigator.of(dialogContext); await engine.dict.saveLanguage(language["id"]); + if (!mounted) return; setState(() {}); - Navigator.of(dialogContext).pop(); + navigator.pop(); } ); }).toList().cast() @@ -212,7 +212,7 @@ class SettingsPageState extends State { } ), ]), - text.info( + TextBlocks.info( title: engine.dict.value("prompt_dir_desc"), context: context, subtitle: "", @@ -230,4 +230,4 @@ class SettingsPageState extends State { ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/chat.dart b/lib/pages/settings/chat.dart index 98cc28c..8d430a3 100644 --- a/lib/pages/settings/chat.dart +++ b/lib/pages/settings/chat.dart @@ -332,7 +332,7 @@ class ChatSettingsPageState extends State { value: engine.chats[engine.currentChat]?["shareLocale"] ?? engine.shareLocale ), ]), - text.info( + TextBlocks.info( title: engine.dict.value("chat_settings_desc"), subtitle: engine.dict.value("chat_settings_subtitle"), action: (){ @@ -355,4 +355,4 @@ class ChatSettingsPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/context.dart b/lib/pages/settings/context.dart index 892f9c8..809c033 100644 --- a/lib/pages/settings/context.dart +++ b/lib/pages/settings/context.dart @@ -92,7 +92,7 @@ class ModelSettingsContextState extends State { data: engine.testPrompt.split("replaceme")[1], ), ), - text.info( + TextBlocks.info( title: engine.dict.value("instructions_desc"), context: context, subtitle: engine.dict.value("recommend_changes_gh"), @@ -115,4 +115,4 @@ class ModelSettingsContextState extends State { ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 1c1ce3a..8c9fa81 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -1,10 +1,6 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; import '../../engine.dart'; -import '../settings.dart'; import '../support/elements.dart'; @@ -105,7 +101,7 @@ class LogsPageState extends State { ); }).toList().reversed.toList(), ), - text.info( + TextBlocks.info( title: engine.dict.value(engine.analytics?"logs_info_analytics":"logs_info_local"), subtitle: "", action: (){}, @@ -122,4 +118,4 @@ class LogsPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/model.dart b/lib/pages/settings/model.dart index 2d6ee6c..cf702f0 100644 --- a/lib/pages/settings/model.dart +++ b/lib/pages/settings/model.dart @@ -101,7 +101,7 @@ class ModelSettingsState extends State { value: engine.shareLocale ), ]), - text.info( + TextBlocks.info( title: engine.dict.value("welcome_available"), context: context, subtitle: "", @@ -119,4 +119,4 @@ class ModelSettingsState extends State { ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/settings/prompt_editor.dart b/lib/pages/settings/prompt_editor.dart index f0bc3c7..70d2e14 100644 --- a/lib/pages/settings/prompt_editor.dart +++ b/lib/pages/settings/prompt_editor.dart @@ -279,7 +279,7 @@ class PromptEditorPageState extends State { }, ) ), - text.infoShort( + TextBlocks.infoShort( title: engine.dict.value("prompt_md_desc"), subtitle: engine.dict.value("prompt_md_docs_link"), action: () async { diff --git a/lib/pages/settings/prompt_viewer.dart b/lib/pages/settings/prompt_viewer.dart index d479118..3d77bd5 100644 --- a/lib/pages/settings/prompt_viewer.dart +++ b/lib/pages/settings/prompt_viewer.dart @@ -154,7 +154,7 @@ class PromptViewerPageState extends State { ), if ((description != null && description.isNotEmpty) && (author != null && author.isNotEmpty) && updatedStr.isNotEmpty) ...[ SliverToBoxAdapter( - child: text.info( + child: TextBlocks.info( title: "$description\n${engine.dict.value("by_author").replaceAll("%author%", author)}\n${engine.dict.value("prompt_last_updated")}: $updatedStr", context: context, subtitle: "", diff --git a/lib/pages/settings/prompts.dart b/lib/pages/settings/prompts.dart index 87a11f9..5e00691 100644 --- a/lib/pages/settings/prompts.dart +++ b/lib/pages/settings/prompts.dart @@ -129,7 +129,7 @@ class PromptsPageState extends State { } ) ]), - text.infoShort( + TextBlocks.infoShort( title: engine.dict.value("prompt_manager_info"), subtitle: "", action: () {}, diff --git a/lib/pages/settings/resources.dart b/lib/pages/settings/resources.dart index 5d6c9bc..906839a 100644 --- a/lib/pages/settings/resources.dart +++ b/lib/pages/settings/resources.dart @@ -73,7 +73,7 @@ class SettingsResourcesState extends State { ); }).toList(), ), - text.info( + TextBlocks.info( title: engine.dict.value("settings_info").replaceAll("%year%", DateFormat('yyyy').format(DateTime.now())), context: context, subtitle: engine.dict.value("gh_repo"), @@ -96,4 +96,4 @@ class SettingsResourcesState extends State { ) ); } -} \ No newline at end of file +} diff --git a/lib/pages/support/elements.dart b/lib/pages/support/elements.dart index 091f1ae..a693f59 100644 --- a/lib/pages/support/elements.dart +++ b/lib/pages/support/elements.dart @@ -603,7 +603,7 @@ class Category { -class text { +class TextBlocks { static Widget info({ required String title, required String subtitle, @@ -781,8 +781,8 @@ class text { } } if(line["user"] == "Gemini"){ - String AIMessage = line["message"]; - if(!(AIMessage == aiChunk) && !(AIMessage == "")) { + String aiMessage = line["message"]; + if(!(aiMessage == aiChunk) && !(aiMessage == "")) { splits.add( Row( mainAxisAlignment: MainAxisAlignment.start, @@ -813,7 +813,7 @@ class text { ); }, selectable: true, - data: AIMessage, + data: aiMessage, ), ), ), diff --git a/lib/storage/resource_repository.dart b/lib/storage/resource_repository.dart index 96a6270..19d5583 100644 --- a/lib/storage/resource_repository.dart +++ b/lib/storage/resource_repository.dart @@ -8,10 +8,24 @@ class ResourceRepository { final VoidCallback notifyEngine; final Future Function(String, String, String)? logEvent; + static const Map _resourceValueOverrides = { + "Application's Google Store page": "https://play.google.com/store/apps/details?id=page.puzzak.paios", + }; + List> resources = []; ResourceRepository({required this.notifyEngine, this.logEvent}); + void _applyResourceOverrides() { + for (final resource in resources) { + final name = resource["name"]?.toString(); + final value = _resourceValueOverrides[name]; + if (value != null) { + resource["value"] = value; + } + } + } + Future initFromHive(String url) async { final box = Hive.box('paios_storage'); @@ -30,6 +44,7 @@ class ResourceRepository { ); } catch (_) {} } + _applyResourceOverrides(); // Step 3: Network refresh and cache update if (!kDebugMode) { @@ -42,7 +57,8 @@ class ResourceRepository { (jsonDecode(response.body) as List).map((e) => Map.from(e)), ); resources = fetched; - box.put("cached_resources_json", response.body); + _applyResourceOverrides(); + box.put("cached_resources_json", jsonEncode(resources)); notifyEngine(); } else { if (logEvent != null) await logEvent!("resource_repo", "error", "Failed to fetch resources: ${response.statusCode}"); diff --git a/test/resource_link_test.dart b/test/resource_link_test.dart new file mode 100644 index 0000000..df5bc3a --- /dev/null +++ b/test/resource_link_test.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Google Play resource points at the current application id', () { + final raw = File('assets/additional_resources.json').readAsStringSync(); + final resources = (jsonDecode(raw) as List) + .cast>(); + final playStoreResource = resources.singleWhere( + (resource) => resource['name'] == "Application's Google Store page", + ); + + expect( + playStoreResource['value'], + 'https://play.google.com/store/apps/details?id=page.puzzak.paios', + ); + }); +}