I've created a GTK 4 app using Gtk-rs. In all the tutorials and documentation I read through to create it, I saw that I should make the application_id something unique such as "org.rk.Counter", which is what I have chosen. Unfortunately, that shows up as the application name in the dock. Here is my code:
fn main() {
// Create a new application
let app = Application::builder()
.application_id("org.rk.Counter")
.build();
// Load CSS and connect to "activate" signal of "app"
app.connect_startup(|_| load_css());
app.connect_activate(build_ui);
// Run the application
app.run();
}
fn build_ui(app: &Application) {
// ...
let window = ApplicationWindow::builder()
.application(app)
.title("rkCounter")
.child(&main_grid)
.build();
window.set_default_size(290, 380);
window.present();
}
The window has the correct title, as set in build_ui(), but here is how it displays on the icon:
How can I change the title for the icon? Should I disregard the advice I saw and change the .application_id()?
The way you create the application id is correct. It's really just meant as a way of uniquely identifying your application.
What you're looking for here is glib::functions::set_application_name()
https://gtk-rs.org/gtk-rs-core/git/docs/glib/fn.set_prgname.html
gtk4::glib::set_prgname(Some("Your app name"));
Related
I have a Java application using the Microsoft Graph SDK to read from Azure AD. I am supposed to translate that application to Rust. In Java, i use code similar to this:
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.identity.TokenCachePersistenceOptions;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;
import java.util.List;
public class GraphSdkClient {
public static void testApplicationClient() {
TokenCachePersistenceOptions options = new TokenCachePersistenceOptions()
.setName("ILM-Demo")
.setUnencryptedStorageAllowed(false);
ClientSecretCredentialBuilder builder = new ClientSecretCredentialBuilder()
.clientId("<MyClientId>")
.tenantId("<MyTenantId>")
.clientSecret("<MyClientSecret>")
.tokenCachePersistenceOptions(options);
TokenCredentialAuthProvider provider = new TokenCredentialAuthProvider(
List.of("Calendars.Read", "Calendars.ReadBasic.All"),
builder.build()
);
GraphServiceClient<?> client = GraphServiceClient
.builder()
.authenticationProvider(provider)
.buildClient();
client.me().calendar().calendarView().buildRequest().get();
}
}
It authenticates as an application, using only the client secret. The permission was given half a year ago and as long as the three values from the ClientSecretCredentialBuilder are correct, it works perfectly fine. Now i tried using a similar conecpt in Rust, taken from the graph-rs-sdk crate:
#[cfg(test)]
mod test {
use graph_rs_sdk::oauth::OAuth;
use warp::Filter;
use crate::{CLIENT_ID, CLIENT_SECRET, TENANT_ID};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct ClientCredentialsResponse {
admin_consent: bool,
tenant: String,
}
#[tokio::test]
async fn ut_client_credentials() {
let query = warp::query::<ClientCredentialsResponse>()
.map(Some)
.or_else(|_| async {
Ok::<(Option<ClientCredentialsResponse>,), std::convert::Infallible>((None,))
});
let routes = warp::get()
.and(warp::path("redirect"))
.and(query)
.and_then(handle_redirect);
// Get the oauth client and request a browser sign in
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
request.browser_authorization().open().unwrap();
warp::serve(routes).run(([127, 0, 0, 1], 8300)).await;
}
async fn handle_redirect(client_credential_option: Option<ClientCredentialsResponse>)
-> Result<Box<dyn warp::Reply>, warp::Rejection> {
match client_credential_option {
Some(client_credential_response) => {
// Print out for debugging purposes.
println!("{:#?}", client_credential_response);
// Request an access token.
request_access_token().await;
// Generic login page response.
Ok(Box::new(
"Successfully Logged In! You can close your browser.",
))
}
None => Err(warp::reject()),
}
}
async fn request_access_token() {
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
let access_token = request.access_token().send().await.unwrap();
println!("{:#?}", access_token);
oauth.access_token(access_token);
}
fn get_oauth_client() -> OAuth {
let mut oauth = OAuth::new();
oauth
.client_id(CLIENT_ID)
.client_secret(CLIENT_SECRET)
.tenant_id(TENANT_ID)
.add_scope("https://graph.microsoft.com/User.Invite.All")
.redirect_uri("http://localhost:8300/redirect")
// .authorize_url("https://login.microsoftonline.com/common/adminconsent")
.access_token_url("https://login.microsoftonline.com/common/oauth2/v2.0/token");
oauth
}
}
Note that i commented out the authorize url. If this url exits, a browser window opens, requesting an admin to log in. This must not happen. When it is commented out, it sends the request directly to <tenantId>/oauth2/v2.0/authorize instead of <tenantId>/oauth2/v2.0/adminconsent, which is what i want, it instead complains: AADSTS900144: The request body must contain the following parameter: 'scope'.. The scope is given though.
I already tried fiddling with urls for hours and also tried every other authentication concept in the crate, but it doesn't seem to work without interaction, which is not possible in my use case. Does someone know how to achieve that? (All permissions are granted as application permissions, not deligated)
Edit request: Please create and add the tag "graph-rs-sdk". I cannot create it myself but knowing the crate being used would be useful.
I am trying to create an Intent that saves a record to a CoreData database. The record will be created if I run the code from the main app, but not in the Intent.
Here is the code:
import Intents
import CoreData
import SwiftUI
let persistenceController = PersistenceController.shared
class IntentHandler: INExtension, DiaryIntentHandling
{
var moc = PersistenceController.shared.context
override func handler(for intent: INIntent) -> Any?
{
guard intent is DiaryIntent else
{
fatalError("Unknwonwn intent type: \(intent)")
}
return self
}
func handle(intent: DiaryIntent, completion: #escaping (DiaryIntentResponse) -> Void)
{
guard let message = message
else
{
completion(DiaryIntentResponse(code: .failure, userActivity: nil))
return
}
completion(DiaryIntentResponse.success(message: message))
let context = PersistenceController.shared.container.viewContext
let myRecord = MyRecord(context: context)
myRecord.timestamp = Date()
myRecord.message = message
do {
try context.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
func resolveMessage(for intent: DiaryIntent, with completion: #escaping (INStringResolutionResult) -> Void)
{
if let message = intent.message
{
completion(INStringResolutionResult.success(with: message))
}
else
{
completion(INStringResolutionResult.needsValue())
}
}
public func confirm(intent: DiaryIntent, completion: #escaping (DiaryIntentResponse) -> Void) {
completion(DiaryIntentResponse(code: .ready, userActivity: nil))
}
}
Do I need to share access to the CoreData database? How do I create the record?
App extensions work like separate apps, so you need to set up an app "group" to share data between them. It gives you a directory that's not part of your app's sandbox that your app and your app extensions can share. Using one requires some setup work:
Turn on app groups by adding the group entitlement. Apple has some documentation on this. I also have a somewhat old blog post that's still accurate as far as setting up the group.
Set up your persistent container use the group directory for Core Data. Normally it saves data in your app's sandbox, but you can tell it to use the app group directory. To do that,
Get a file URL for the directory using FileManager's function containerURL(forSecurityApplicationGroupIdentifier:). The argument is the same as your app group identifier.
Make sure this directory exists! It doesn't get created automatically. Use FileManager.default.fileExists(atPath:) to check if it exists, and if not, use FileManager.default.createDirectory(at:withIntermediateDirectories:attributes:) to create it.
Use a NSPersistentStoreDescription to tell your persistent container to use that URL for Core Data. That would be something like
let persistentContainer = NSPersistentContainer(name: containerName)
let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreUrl)
persistentStoreDescription.type = NSSQLiteStoreType
persistentContainer.persistentStoreDescriptions = [ persistentStoreDescription ]
After the previous step, the persistent container won't be able to find any data that's currently in Core Data. So:
If your app has not already been released, delete it from your test devices and simulators and then rebuild. You'll get a new persistent store in the app group directory.
If your app has already been released, add code to copy the persistent store from the current location to the new location. Do this before loading the persistent store, and only do it if the copy in the app group doesn't already exist (so you don't re-copy old data). The best way to do that is with the migratePersistentStore(_:to:options:withType:) function from NSPersistentStoreCoordinator. Don't just copy your SQLite file over, because that won't include all the data.
Android allows specifying a Javascript interface to act as a bridge between the webview and Android code. Similarly, iOS provides the UiWebView stringByEvaluatingJavaScriptFromString method that allows communicating with the underlying webview.
But the issue is that once we build the flutter web app and say we include it as part of a web view in android , Then whenever communication needs to take place , there is some amount of code that we should write in both android side and javascript side manually.
Since editing javascript code generated from flutter web app build is impractical , we need a way to establish a communication channel on flutter side which will talk to the native side using the same type of javascript bridge above. This will be something similar to the method channels when using the flutter app on the native side directly.
So how would we inject or add our custom javascript code from inside flutter so that it will be added during compiling the js file ?
You can create a webmessage channel on the android side (iOS has an equivalent).
There is a description of how to do it here:
How Do You Use WebMessagePort As An Alternative to addJavascriptInterface()?
A brief setup on the javascript side looks like this - you would need to implement this in dart html:
const channel = new MessageChannel();
var nativeJsPortOne = channel.port1;
var nativeJsPortTwo = channel.port2;
window.addEventListener('message', function(event) {
if (event.data != 'capturePort') {
nativeJsPortOne.postMessage(event.data)
} else if (event.data == 'capturePort') {
/* The following three lines form Android class 'WebViewCallBackDemo' capture the port and assign it to nativeJsPortTwo
var destPort = arrayOf(nativeToJsPorts[1])
nativeToJsPorts[0].setWebMessageCallback(nativeToJs!!)
WebViewCompat.postWebMessage(webView, WebMessageCompat("capturePort", destPort), Uri.EMPTY) */
if (event.ports[0] != null) {
nativeJsPortTwo = event.ports[0]
}
}
}, false);
nativeJsPortOne.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortTwo.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortOne.start();
nativeJsPortTwo.start();
A similar solution for listening over using an eventlistener on the window in flutter web is below:
window.onMessage.listen((onData) {
print(onData.toString());
MessageEvent messageEvent = onData;
for (Property property in properties) {
if (messageEvent.data["id"] == property.id) {
});
} else if (messageEvent.data["northEastLng"] != null) {
print(messageEvent.data["northEastLat"]);
if (double.parse(messageEvent.data["northEastLat"]) == bounds.northEast.lat && double.parse(messageEvent.data["northEastLng"]) == bounds.northEast.lng){
return;
}
LatLng southWest = new LatLng(double.parse(messageEvent.data["southWestLat"]), double.parse(messageEvent.data["southWestLng"]));
LatLng northEast = new LatLng(double.parse(messageEvent.data["northEastLat"]), double.parse(messageEvent.data["northEastLng"]));
LatLngBounds incomingBounds = new LatLngBounds(southWest, northEast);
if (incomingBounds == bounds) {
return;
}
bounds = incomingBounds;
_propertyListPresenter.filterBounds(bounds);
}
}
});
I am trying to perform history tracking in my CoreData+CloudKit project which uses NSPersistentCloudKitContainer. I have been following along with Apple's sample project
I want to perform certain task when the remote store has been updated. For this apple recommends enabling remote notification in the Signing & capabilities's Background Mode section of the app.
I have enabled History Tracking for my project as shown in Apple's sample project.
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// ...
Also I have registered my store to listen for store changes.
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// ...
Observer is also added to listen for NSPersistentStoreRemoteChangeNotification.
However there is no NSPersistentStoreRemoteChangeNotification being fired. To make sure there is no mistake in my implementation, I am have simply put breakpoints in #objc func storeRemoteChange(_ notification: Notification) the Apple's provided sample code but still I can not see any notification being fired and no breakpoints are activated.
I have understood the deduplication of the Tags done in the sample project and also tried testing it but without any success. Is it a bug in the Apple's implementation or am I missing any setup which is required?
My guess is you are observing the container instead of the store coordinator, add your observer like this:
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Note the last param container.persistentStoreCoordinator
And a warning, this notification comes in on all different threads so you be careful with concurrency. Just put a 5 second sleep in the method and you'll see on app launch 3 different threads call it. This is likely why in the example there is a historyQueue with maxOperationCount 1 to handle it.
Some notifications have NSPersistentHistoryTokenKey in the userInfo not sure why.
Debugging the sample app mentioned by the OP, I observed the following:
As of XCode Version 11.3 (11C29), there are SDK constants both for the option key (NSPersistentStoreRemoteChangeNotificationPostOptionKey) and for the notification name (.NSPersistentStoreRemoteChange), and these are reflected in the latest download of the sample code.
The sample app registers for the remote change notifications on the wrong object, so it never receives any. Changing the sender as per the accepted answer fixes this.
The app UI always updates to reflect changes received from the cloud, but those updates are prompted not by remote change notifications but by the app's NSFetchedResultsController delegate using the controllerDidChangeContent callback to refresh the UI.
The standard NSPersistentCloudKitContainer used by the sample app is doing automatic imports into the local persistent store of all the cloud-sent updates and, because the persistentStore is set up for history tracking and the viewContext is set up to auto-update to the latest generation of data, each import triggers a UI update.
Based on these observations, I wrote a small app from scratch based on the XCode template you get by specifying use of CoreData, CloudKit, and SwiftUI. I set up its persistent container and view context the same way they are set up in the sample app, and used SwiftUI's #FetchRequest wrapper to obtain the data in the master view display. Sure enough, I saw the exact same remote import behavior without using any remote change notifications, and the UI updated after each import.
I then confirmed that, as per the accepted answer, if I registered for remote change notifications correctly, they would be received. They seem to be sent after each receive and import operation in the NSPersistentCloudKit completes. Observing them is not needed to get notifications of the local data changes initiated by those imports.
I was able to reliably echo Core Data changes via iCloud between two devices in my project. But I reached a point where I needed access to the change history. Apple has nicely described the steps to set it up in Consuming Relevant Store Changes
I followed along and happily copy and pasted the relevant code into my app. But the NSPersistentStoreRemoteChange notification was not coming through. As in comedy, timing is everything. Per the documentation for persistentStoreDescriptions I
If you will be configuring custom persistent store descriptions, you
must set this property before calling
loadPersistentStores(completionHandler:)
I was configuring persistentStoreDescriptions inside of loadPersistentStores(completionHandler:) So the painfully obvious way to do it is setup the following code in the AppDelegate.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentCloudKitContainer(name: "yourProjectNameGoesHere")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// this will make background updates from iCloud available to the context.
container.viewContext.automaticallyMergesChangesFromParent = true
// call this LAST, after the persistentStoreDescriptions configuration.
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
Catch the notification from your view controller or model.
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(fetchChanges),
name: .NSPersistentStoreRemoteChange,
object: pc.persistentStoreCoordinator)
}
#objc func fetchChanges(note: Notification) {
print("Just received a NSPersistentStoreRemoteChange notification")
}
I don't know whether it's a bug. Simply downloading and running the Apple's Sample Project but the NSPersistentStoreRemoteChangeNotification is never fired.
I added one more observer for the same NSPersistentStoreRemoteChangeNotification in my AppDelegate and it is firing.
I added notification observer in AppDelegate and then simply call the StoreRemoteChange(_:) of the CoreDataStack. Also, Tag deduplication logic works properly.
Here is the code which I added in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// The view controller hierarchy is defined in the main storyboard.
guard let splitViewController = window?.rootViewController as? UISplitViewController,
let navController = splitViewController.viewControllers[splitViewController.viewControllers.count - 1] as? UINavigationController,
let topViewController = navController.topViewController else {
return false
}
// Configure the splitViewController.
topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
splitViewController.preferredDisplayMode = .allVisible
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: nil)
return true
}
#objc
func storeRemoteChange(_ notification: Notification) {
coreDataStack.storeRemoteChange(notification)
}
SwiftUI
Here's a way to be notified of CloudKit remote changes in a SwiftUI view, and, say, update the contents of a List that would depend on a #FetchRequest--not shown in the code for simplicity:
struct MyView: View {
#State var refresh = UUID()
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
var body: some View {
List {
// ...
}
.id(refresh)
.onReceive(self.didRemoteChange) { _ in
self.refresh = UUID()
}
}
}
Note: .receive(on: RunLoop.main) is necessary in order to avoid modifying the UI from a background thread, as the remote event could (and will) otherwise fire from a background thread. Alternatively, .receive(on: DispatchQueue.main) can also be used.
For that to work, the NSPersistentCloudKitContainer needs to be set up to fire events when remote changes occur:
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "YourApp")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
//
// Generate notifications upon remote changes
//
container.persistentStoreDescriptions.forEach {
$0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
}
I am in the depths of creating a mobile web game that I will soon release. I am interested in monetizing this app. I want the game to feel like its a regular app and therefore wish to have admob ads (with deep links to the app store). I have heard that these also have substantially higher cpm that adsense mobile. My question is this: if I "port" admob using node.js will the clicks and views be recorded as server side, i.e. coming from one place, or mobile, i.e. from the user?
Here are some resources I am thinking of using:
https://media.admob.com/api/v1/docs/
https://github.com/floatinghotpot/cordova-plugin-admob
Any thoughts?
I am the author of the plugin you mentioned: https://github.com/floatinghotpot/cordova-plugin-admob
The basic version has been deprecated, and the pro version is recommended: https://github.com/floatinghotpot/cordova-admob-pro
Instead of porting AdMob to mobile web, you can consider use Cordova to pack your html5 game as a hybrid APP, then publish to Apple AppStore or Google Play Store.
Use the AdMob plugin to present Ad in your app, then Google will pay you.
Only a few javascript lines to present the Ad, and the AdMob SDK will take care of Ad presenting and user click.
See the example code:
function onLoad() {
if(( /(ipad|iphone|ipod|android|windows phone)/i.test(navigator.userAgent) )) {
document.addEventListener('deviceready', initApp, false);
} else {
initApp();
}
}
var admobid = {};
if( /(android)/i.test(navigator.userAgent) ) {
admobid = { // for Android
banner: 'ca-app-pub-6869992474017983/9375997553',
interstitial: 'ca-app-pub-6869992474017983/1657046752'
};
} else if(/(ipod|iphone|ipad)/i.test(navigator.userAgent)) {
admobid = { // for iOS
banner: 'ca-app-pub-6869992474017983/4806197152',
interstitial: 'ca-app-pub-6869992474017983/7563979554'
};
} else {
admobid = { // for Windows Phone
banner: 'ca-app-pub-6869992474017983/8878394753',
interstitial: 'ca-app-pub-6869992474017983/1355127956'
};
}
// it will display smart banner at top center, using the default options
if(AdMob) AdMob.createBanner( {
adId: admobid.banner,
position: AdMob.AD_POSITION.TOP_CENTER,
autoShow: true } );