Brand new and naive to pebble SDK and C but lots of experience in other languages. Which means I'm probably going to be embarrassed when someone solves this for me very easily but... I have two files in a watchface project based on the tutorials... I'm pulling in different data but it all works just like the getting started tutorial up until the point that the code updates one of the text layers with... nothing. It's got me puzzled becuase the data it's supposed to be putting in there appears fine in the console log so it's definitely getting passed over. Anyways, here's the C
include
#define KEY_MONTHMILES 0
#define KEY_MONTHELEVATION 1
#define KEY_TOTALMILES 2
#define KEY_TOTALELEVATION 3
static Window *s_main_window;
static TextLayer *s_time_layer;
static BitmapLayer *s_background_layer;
static GBitmap *s_background_bitmap;
static TextLayer *s_weather_layer;
static void update_time() {
// Get a tm structure
time_t temp = time(NULL);
struct tm *tick_time = localtime(&temp);
// Create a long-lived buffer
static char buffer[] = "00:00";
// Write the current hours and minutes into the buffer
if(clock_is_24h_style() == true) {
// Use 24 hour format
strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
} else {
// Use 12 hour format
strftime(buffer, sizeof("00:00"), "%I:%M", tick_time);
}
// Display this time on the TextLayer
text_layer_set_text(s_time_layer, buffer);
}
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
update_time();
}
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
// Store incoming information
static char monthlyMiles_buffer[128];
static char monthlyElevation_buffer[128];
static char yearlyMiles_buffer[128];
static char yearlyElevation_buffer[128];
// Read first item
Tuple *t = dict_read_first(iterator);
// For all items
while(t != NULL) {
// Which key was received?
switch(t->key) {
case KEY_MONTHMILES:
snprintf(monthlyMiles_buffer, sizeof(monthlyMiles_buffer), "%d", (int)t->value->int32);
break;
case KEY_MONTHELEVATION:
snprintf(monthlyElevation_buffer, sizeof(monthlyElevation_buffer), "%d", (int)t->value->int32);
break;
case KEY_TOTALMILES:
snprintf(yearlyMiles_buffer, sizeof(yearlyMiles_buffer), "%d", (int)t->value->int32);
break;
case KEY_TOTALELEVATION:
snprintf(yearlyElevation_buffer, sizeof(yearlyElevation_buffer), "%d", (int)t->value->int32);
break;
default:
APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
break;
}
// Look for next item
t = dict_read_next(iterator);
}
text_layer_set_text(s_weather_layer, monthlyMiles_buffer);
}
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
}
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
}
static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
}
static void main_window_load(Window *window) {
// Create GBitmap, then set to created BitmapLayer
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_BG_IMAGE);
s_background_layer = bitmap_layer_create(GRect(0, 0, 144, 168));
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_background_layer));
// Create time TextLayer
s_time_layer = text_layer_create(GRect(0, 60, 144, 50));
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_text_color(s_time_layer, GColorWhite);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_time_layer));
// Improve the layout to be more like a watchface
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
// Make sure the time is displayed from the start
update_time();
// Create temperature Layer
s_weather_layer = text_layer_create(GRect(0, 130, 144, 25));
text_layer_set_background_color(s_weather_layer, GColorClear);
text_layer_set_text_color(s_weather_layer, GColorWhite);
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
text_layer_set_text(s_weather_layer, "Loading...");
text_layer_set_font(s_weather_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
}
static void main_window_unload(Window *window) {
// Destroy TextLayer
text_layer_destroy(s_time_layer);
// Destroy GBitmap
gbitmap_destroy(s_background_bitmap);
// Destroy BitmapLayer
bitmap_layer_destroy(s_background_layer);
// Destroy weather elements
text_layer_destroy(s_weather_layer);
}
static void init() {
// Create main Window element and assign to pointer
s_main_window = window_create();
// Set handlers to manage the elements inside the Window
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload
});
// Show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
// Register with TickTimerService
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
// Register callbacks
app_message_register_inbox_received(inbox_received_callback);
app_message_register_inbox_dropped(inbox_dropped_callback);
app_message_register_outbox_failed(outbox_failed_callback);
app_message_register_outbox_sent(outbox_sent_callback);
// Open AppMessage
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
}
static void deinit() {
// Destroy Window
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
and here's the Javascript
var xhrRequest = function (url, type, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
callback(this.responseText);
};
xhr.open(type, url);
xhr.send();
};
function getStats() {
// Construct URL
var url = "http://crin.co.uk/stravaWatchface/getStats.php";
// Send request to crin.co.uk
xhrRequest(url, 'GET',
function(responseText) {
// responseText contains a JSON object with stat info
var json = JSON.parse(responseText);
// Monthly miles
var monthMiles = json.mM;
console.log("Monthly total miles is: " + monthMiles);
// Monthly Elevation
var monthElevation = json.mE;
console.log("Monthly total elevation gain is: " + monthElevation);
// Total Miles
var totalMiles = json.tM;
console.log("All-time total miles is: " + totalMiles);
// Total Elevation
var totalElevation = json.tE;
console.log("All-time total elevation gain is: " + totalElevation);
// Assemble dictionary using our keys
var dictionary = {
"KEY_MONTHMILES": monthMiles,
"KEY_MONTHELEVATION": monthElevation,
"KEY_TOTALMILES": totalMiles,
"KEY_TOTALELEVATION": totalElevation
};
// Send to Pebble
Pebble.sendAppMessage(dictionary,
function(e) {
console.log("Stats info sent to Pebble successfully!");
},
function(e) {
console.log("Error sending stats info to Pebble!");
}
);
}
);
}
// Listen for when the watchface is opened
Pebble.addEventListener('ready',
function(e) {
console.log("PebbleKit JS ready!");
// Get the initial weather
getStats();
}
);
// Listen for when an AppMessage is received
Pebble.addEventListener('appmessage',
function(e) {
console.log("AppMessage received!");
getStats();
}
);
Anyone spot anything obvious? It compiles and installs fine, but this line...
text_layer_set_text(s_weather_layer, monthlyMiles_buffer);
Is updating the watchface with blank space and not the value it should.
Please help I've lost an afternoon to this.
Related
I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possible be up to 200 or more images being loaded I am try to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also see some lag as images are still getting load while scrolling up and down the list. Also when I scroll back to the top of the list place holders that previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.
When I scroll back to the song list and then back to the artist list the view is reset but it still has a lot of delay when going into the artist tab.
This image is what the screen looks like when first entering the Artist tab.
This image is what the screen looks like after scrolling to the bottom of the list and back to the top.
As you can see the <unknow. and AJR have had their default image replaced.
Here is my code that I am calling to load the images from Wikipedia.
#Override
public void onBindViewHolder(#NonNull ARV holder, int position) {
Artist artist = artistList.get(position);
if(artist!=null) {
holder.artistName.setText(artist.artistName);
String bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
ImageLoader.getInstance().displayImage(imageUrl, holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(true).build());
} catch (IOException e) {
e.printStackTrace();
}
/*ImageLoader.getInstance().displayImage(getCoverArtPath(context,artist.id),holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(false).build());*/
}
}
private StringBuilder getUrlSource(String site) throws IOException {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL localUrl = null;
localUrl = new URL(site);
URLConnection conn = localUrl.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line = "";
String html;
StringBuilder ma = new StringBuilder();
while ((line = reader.readLine()) != null) {
ma.append(line);
Log.i(ContentValues.TAG, "StringBuilder " + ma);
}
Log.i(ContentValues.TAG, "Final StringBuilder " + ma);
return ma;
}
public static String cutImg(StringBuilder split){
int start=split.indexOf("\"source\":")+new String("\"source\":\"").length();
split.delete(0, start);
split.delete(split.indexOf("\""), split.length());
Log.i(ContentValues.TAG, "StringBuilder " + split);
return split.toString();
}
Here is the code that is call the Artist Fragment.
public class ArtistFragment extends Fragment {
int spanCount = 3; // 2 columns
int spacing = 20; // 20px
boolean includeEdge = true;
private RecyclerView recyclerView;
private ArtistAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_artist, container, false);
recyclerView = view.findViewById(R.id.artistFragment);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
Thread t = new Thread()
{
public void run()
{
// put whatever code you want to run inside the thread here.
new LoadData().execute("");
}
};
t.start();
return view;
}
public class LoadData extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
if(getActivity()!=null) {
adapter=new ArtistAdapter(getActivity(),new ArtistLoader().artistList(getActivity()));
}
return "Executed";
}
#Override
protected void onPostExecute(String s) {
recyclerView.setAdapter(adapter);
if(getActivity()!=null) {
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
}
}
I have also tried this using Picasso using the following code:
bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
Picasso.get().load(imageUrl).placeholder(R.drawable.album)
.error(R.drawable.artistdefault).into(holder.artistImage);
} catch (IOException e) {
e.printStackTrace();
}
The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been try for several days to fix this, I have tried several different examples that I found on Stack overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly.
Thanks in advance.
ArtistFragmentconverted to Kotlin
class ArtistFragment : Fragment() {
var spanCount = 3 // 2 columns
var spacing = 20 // 20px
var includeEdge = true
var retrofit: Retrofit? = null
var wikiService: WikiService? = null
var adapter: ArtistAdapter? = null
private var recyclerView: RecyclerView? = null
private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var progress_view: ProgressBar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_artist, container, false)
recyclerView = view.findViewById(R.id.artistFragment)
recyclerView?.setLayoutManager(GridLayoutManager(activity, 3))
progress_view = view.findViewById(R.id.progress_view)
initWikiService()
initList()
//LoadData().execute("")
return view
}
override fun onDestroy() {
super.onDestroy()
viewModelJob.cancel()
}
private fun initList() {
recyclerView!!.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
adapter = ArtistAdapter(this)
adapter?.items = ArrayList()
adapter?.listener = this
recyclerView?.adapter = adapter
viewModelScope.launch {
progress_view.visibility = View.VISIBLE
val wikiPages = getWikiPages()
adapter?.items = wikiPages
progress_view?.visibility = View.GONE
}
}
private suspend fun getWikiPages(): ArrayList<Artist> {
val newItems = ArrayList<Artist>()
withContext(Dispatchers.IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
}
}
}
}
return newItems
}
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
I believe I have resolved most of the issues I was previously seeing I am now down to the following problems:
Artist.item.map { artist -> - Not sure how this should be called, Unresolved reference: item
}.awaitAll().forEach { response -> = forEach is telling me Overload resolution ambiguity. All these functions match.
public inline fun Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) → Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) → Unit): Unit defined in kotlin.collections
newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.
I have reworked the ArtistAdapter not sure if it is correct though.
class ArtistAdapter(private val context: ArtistFragment, private val artistList: List<Artist>?) : RecyclerView.Adapter<ArtistAdapter.ARV>() {
private var dimension: Int = 64
init {
val density = context.resources.displayMetrics.density
dimension = (density * 64).toInt()
hasStableIds()
}
var items: MutableList<Artist> = ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
var listener: Listener? = null
interface Listener {
fun onItemClicked(item: Artist)
abstract fun ArtistAdapter(context: ArtistFragment): ArtistAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ARV {
return ARV(LayoutInflater.from(parent.context).inflate(R.layout.artist_gride_item, parent,
false))
}
override fun onBindViewHolder(holder: ARV, position: Int) {
holder.onBind(getItem(position))
}
private fun getItem(position: Int): Artist = items[position]
override fun getItemId(position: Int): Long = items[position].id
override fun getItemCount(): Int {
return artistList?.size ?: 0
}
inner class ARV(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val artistNameView: TextView = itemView.findViewById(R.id.artistName)
private val artistAlbumArtView: SquareCellView = itemView.findViewById(R.id.artistAlbumArt)
fun onBind(item: Artist) {
artistNameView.text=item.artistName
if(item.artistURL!=null) {
Picasso.get()
.load(item.artistURL)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.artistdefualt)
.into(artistAlbumArtView)
} else {
artistAlbumArtView.setImageResource(R.drawable.artistdefualt)
}
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val artistId = artistList!![bindingAdapterPosition].id
val fragmentManager = (context as AppCompatActivity).supportFragmentManager
val transaction = fragmentManager.beginTransaction()
val fragment: Fragment
transaction.setCustomAnimations(R.anim.layout_fad_in, R.anim.layout_fad_out,
R.anim.layout_fad_in, R.anim.layout_fad_out)
fragment = ArtistDetailsFragment.newInstance(artistId)
transaction.hide(context.supportFragmentManager
.findFragmentById(R.id.main_container)!!)
transaction.add(R.id.main_container, fragment)
transaction.addToBackStack(null).commit()
}
}
}
Logcat Snippet
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or it
s super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
at okhttp3.internal.Util.<clinit>(Util.java:87)
at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
at okhttp3.HttpUrl.get(HttpUrl.java:917)
at retrofit2.Retrofit$Builder.baseUrl(Retrofit.java:506)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.initWikiService(ArtistFragment.kt:103)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.onCreateView(ArtistFragment.kt:43)
It looks to me as a known bug with Picasso.
Try to load default image manually so it won't be replaced with cached one.
Update 14.10.20:
I think the main problem is that you load network content in adapter in rather ineffective way. I suggest to form a list of all urls at first, leaving only image load in adapter.
Also reccomend you to use rerofit2 for network calls and something for async work instead of AsyncTask: rxJava, courutines, flow etc.
I created a sample project to load data async using retrofit2+coroutines.
In activity:
private val viewModelScope = CoroutineScope(Dispatchers.Main)
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
private fun initList() {
viewModelScope.launch {
val wikiPages = getWikiPages()
adapter?.items = wikiPages
}
}
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private suspend fun getWikiPages(): ArrayList<Item> {
val newItems = ArrayList<Item>()
withContext(IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Item(id, title, url))
}
}
}
}
return newItems
}
In viewHolder:
fun onBind(item: Item) {
if (item.url != null) {
Picasso.get()
.load(item.url)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.ic_baseline_broken_image_24)
.into(pictureView)
} else {
pictureView.setImageResource(R.drawable.ic_baseline_image_24)
}
}
In adapter: add hasStableIds() to constructor and override getItemId method:
init {
hasStableIds()
}
override fun getItemId(position: Int): Long = items[position].id
Retrofit Service:
interface WikiService {
#GET("/w/api.php?action=query&prop=pageimages&format=json&pithumbsize=250")
suspend fun getWikiData(#Query("titles") band: String): Response<WikipediaResponse?>
}
I tried to upgrade CefSharp from Version 69.0.0.0 to 79.1.36.
I could not get the Javascript interaction working.
The registration changed from
this.Browser.RegisterJsObject
to
this.Browser.JavascriptObjectRepository.Register
according to https://github.com/cefsharp/CefSharp/issues/2990.
When I execute EvaluateScriptAsync, I get a response back with Status Canceled.
Trying to understand how to implement it correctly, I examined the CefSharp.WpfExample and noticed that the Javascript functionality in the example WPF application does not work either.
The Execute Javascript (asynchronously) does not do anything when clicking the Run button.
The Evaluate Javascript (Async) returns:
Uncaught ReferenceError: bound is not defined # about:blank:1:0
Did the Javascript functionality break in the latest release?
Update
Here is how it is used in our code.
This is the registration
public void RegisterJavaScriptHandler(string name, object handler)
{
try
{
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
this.Browser.JavascriptObjectRepository.Register(name, handler, false, new BindingOptions() { CamelCaseJavascriptNames = false });
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
This is the EvaluateScriptAsync part
public void InitializeLayers()
{
try
{
int count = _mapLogic.Layers.Count();
foreach (WMSLayer layer in _mapLogic.Layers)
{
if (!_loadedLayers.Contains(layer))
{
var script = string.Format("addWMSLayer('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}')",
layer.ProviderCode.Url, layer.AttributionText, layer.AttributionHref,
layer.Layer, layer.FormatCode.Format, layer.ServerType, layer.Res1, layer.Res2, layer.Res3, layer.Res4);
var response = this.ECBBrowser.Browser.EvaluateScriptAsync(script, new TimeSpan(0, 0, 1));
response.ContinueWith(t =>
{
count--;
if (count == 0) this.initializeMap();
});
_loadedLayers.Add(layer);
}
else
{
count--;
if(count == 0) this.initializeMap();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
Update II
I now believe something has changed with the resource loading.
This is what I have (unimportant parts are left out).
public class ECBSchemeHandler : IResourceHandler
{
private string _mimeType;
private Stream _stream;
public bool Open(IRequest request, out bool handleRequest, ICallback callback)
{
var result = open(request, callback);
handleRequest = result;
return result;
}
public bool Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback)
{
return read(dataOut, out bytesRead, callback);
}
public bool ReadResponse(Stream dataOut, out int bytesRead, ICallback callback)
{
return read(dataOut, out bytesRead, callback);
}
private bool open(IRequest request, ICallback callback)
{
var u = new Uri(request.Url);
var file = u.Authority + u.AbsolutePath;
var ass = Assembly.GetExecutingAssembly();
var resourcePath = ECBConfiguration.DEFAULT_ASSEMBLY_NAMESPACE + "." + file.Replace("/", ".");
if (ass.GetManifestResourceInfo(resourcePath) != null)
{
Task.Run(() =>
{
using (callback)
{
_stream = ass.GetManifestResourceStream(resourcePath);
var fileExtension = Path.GetExtension(file);
_mimeType = ResourceHandler.GetMimeType(fileExtension);
callback.Continue();
}
});
return true;
}
else
{
callback.Dispose();
}
return false;
}
private bool read(Stream dataOut, out int bytesRead, IDisposable callback)
{
callback.Dispose();
if (_stream == null)
{
bytesRead = 0;
return false;
}
//Data out represents an underlying buffer (typically 32kb in size).
var buffer = new byte[dataOut.Length];
bytesRead = _stream.Read(buffer, 0, buffer.Length);
dataOut.Write(buffer, 0, buffer.Length);
return bytesRead > 0;
}
}
}
Using the built-in ResourceHandlers as pointed out by #amaitland solved the problem with the Javascript registration.
I'm in the middle of finishing a page to edit your profile and settings. To save the data from the dynamic inputs, I used Ajax to transfer the data to the server, which then stores the data in a dictionary, into the Session, as well as the name of the menu you would like to save.
After that, I call a post back with JavaScript.
In c# on the page load, if it is a post back it will call the SaveData function, which checks the Session for the menu name that you would like to save, and will call the right function accordingly(SaveProfile, SaveNotifications, SavePrivacy, ect..).
My problem is its only saving half of the data from the Dynamic Inputs.
There is 6 different lists that have dynamic inputs, 3 are just a plain Text Input, the other 3 have a Select with a Text Input.
The ones that get saved are totally random, sometimes it will only save social links, sometimes it will only save skills, and you get the idea.
I think its happening because Ajax functions are asynchronous, and when the post back is triggered, not all the data was saved to the session.
There is too much code to post all of it, so if you want to see other stuff just tell me what you would like to see.
Creating the onclick save events.
var dynamicListIds = ["skills", "hobbies", "games"];
var dynamicSelectListIds = ["socialLinks", "contactInfo", "systemSpecs"];
$(document).ready(function () {
$("a[save=all]").on('click', function () { MultipleDataPassToServer(dynamicListIds, dynamicSelectListIds, 'all'); });
$("a[save=profile]").on('click', function () { MultipleDataPassToServer(dynamicListIds, dynamicSelectListIds, $(this).attr('save')); });
$("a[save][save!=all][save!=profile]").on('click', function () { CallDataPassedEvent($(this).attr('save')); });
});
Collecting and transferring data.
//Begins the procedure of collection and passing data to the server.
function MultipleDataPassToServer(listIDs, selectListIDs, save) {
if (listIDs != null) {
for (var i = 0; i < listIDs.length; i++) {
TransferInputDataToHiddenField(listIDs[i]);
}
}
if (selectListIDs != null) {
for (var i = 0; i < selectListIDs.length; i++) {
TransferSelectInputDataToHiddenField(selectListIDs[i]);
}
}
CallDataPassedEvent(save);
}
//Gets all values from input elements,
//combines them into one string
//and passes the data to the server
function TransferInputDataToHiddenField(id)
{
var inputElements = document.getElementsByName("input" + id);
//var inputElements = $('#input' + id);
var inputValues = "";
for (var i = 0; i < inputElements.length; i++)
{
inputValues += inputElements[i].value;
if (i != inputElements.length - 1)
{
inputValues += "|";
}
}
CallPassDataToServer(id, inputValues);
}
//Gets all values from select and input elements,
//combines them into one string
//and passes the data to the server
function TransferSelectInputDataToHiddenField(id) {
var inputElements = document.getElementsByName("input" + id);
var selectElements = document.getElementsByName("select" + id);
//var inputElements = $('#input' + id);
var inputValues = "";
for (var i = 0; i < inputElements.length; i++) {
if ($(selectElements[i]).val() == "Custom") {
inputValues += $('#' + id + "Custom" + i).val() + "=";
}
else
{
inputValues += selectElements[i].value + "=";
}
inputValues += inputElements[i].value;
if (i != inputElements.length - 1) {
inputValues += "|";
}
}
CallPassDataToServer(id, inputValues);
}
//Passes data asynchronously to the server
function CallPassDataToServer(id, clientData) {
PageMethods.PassDataToServer(id, clientData, onSucceeded, onFailed);
}
//Calls event to tell server that either the data has been passed,
//or no data needs to be passed.
//save is a string which tells the server which forms's data will be saved.
//pass 'all' into save for the server to save all forms.
function CallDataPassedEvent(save)
{
PageMethods.CallEventDataPassed(save, onDataPassed, onFailed);
}
function onDataPassed(result, userContext, methodName) {
__doPostBack();
}
C# Server Code
[System.Web.Services.WebMethod()]
public static string PassDataToServer(String id, String clientData)
{
if (!dynamicInputs.ContainsKey(id))
dynamicInputs.Add(id, clientData);
else
dynamicInputs[id] = clientData;
return "Debug - Data Passed : " + clientData;
}
[System.Web.Services.WebMethod()]
public static void CallEventDataPassed(String menu)
{
System.Web.HttpContext.Current.Session["AccountSave"] = menu;
System.Web.HttpContext.Current.Session["ProfileDynamicInputs"] = dynamicInputs;
//I tried calling the postback from here but it just gives me an error client side. (OnFailed)
//System.Web.HttpContext.Current.Response.Redirect(System.Web.HttpContext.Current.Request.RawUrl);
//EventDataPassed(menu);
}
C# OnLoad
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
SaveData((string)System.Web.HttpContext.Current.Session["AccountSave"]);
}
}
[UserAction.Authorize.Account]
[UserAction.Authorize.Restrictions(UserAction.Authorize.AuthorizationRule.RequireAll, new int[] { 2, 11 }, true)]
protected void SaveData(string menu)
{
switch (menu)
{
case "all":
SaveProfileData();
break;
case "profile":
SaveProfileData();
break;
case "billing":
break;
case "privacy":
break;
case "notifications":
break;
case "account":
break;
}
}
Here is an image of the site, knowing what something looks like helps me think.
After I save only some of the options will still be there, seemingly random ones, and if I save again, more disappear, if not all of them.
Every time the page loads, it recreates the dynamic inputs with c#, if you need to see that just ask.
Thanks
I have recently created a new plugin for an android app I've been working on. This plugin requires the use of a remember me function that I cant seem to get working properly. Within my code I've designated the use of shared properties at the top and the string pass.
View thisScreensView = null;
String errormessage = "";
String errormessageTextColor = "";
String errormessageText = "";
String rememberTextText = "";
String rememberTextTextColor = "";
String voucherTextfieldPlaceholder = "";
String voucherTextfieldSecureTextEntry = "";
boolean voucherTextfieldSecureTextEntryBool = false;
String iphoneImage = "";
String ipadImage = "";
String errormessageInvalid = "";
String errormessageRemember = "";
private TextView errormessageTextView = null;
private EditText voucherTextfieldEditText = null;
private ImageView imageView = null;
private Button submitButton = null;
private Switch rememberSwitch = null;
private Drawable myHeaderImageDrawable = null;
private String alphaImage = "", pass;
private static BT_item screenObjectToLoad = null;
SharedPreferences sharedpreferences;
I have it assigning the variable pass into the designated text box with this code:
voucherTextfieldEditText.setVisibility(View.VISIBLE);
rememberSwitch = (Switch) thisScreensView.findViewById(R.id.rememberSwitch);
// set the switch to ON
rememberSwitch.setText(errormessageRemember);
rememberSwitch.setChecked(true);
// attach a listener to check for changes in state
rememberSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
BT_debugger.showIt(fragmentName + ":rememberSwitch is ON");
// switchStatus.setText("Switch is currently ON");
//voucherTextfieldEditText.setText(pref.getString(pass, null));
} else {
BT_debugger.showIt(fragmentName + ":rememberSwitch is OFF");
// switchStatus.setText("Switch is currently OFF");
}
}
});
// check the current state before we display the screen
if (rememberSwitch.isChecked()) {
BT_debugger.showIt(fragmentName + ":rememberSwitch is ON");
// switchStatus.setText("Switch is currently ON");
//voucherTextfieldEditText.setText(pass);
if (voucherTextfieldEditText!=null) {
//voucherTextfieldEditText.setText(sharedPreferences.getString(pass, ""));
}
if (pass != null) {
String textfield = sharedpreferences.getString("PASS", pass);
//voucherTextfieldEditText.setText(sharedPreferences.getString("PASS", pass));
voucherTextfieldEditText.setText(textfield);
}
}
else {
BT_debugger.showIt(fragmentName + ":rememberSwitch is OFF");
// switchStatus.setText("Switch is currently OFF");
}
//
// submit click listener..
submitButton = (Button) thisScreensView.findViewById(R.id.submitButtonVoucher);
submitButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
launchScreen(voucherTextfieldEditText.getText().toString());
}
});
// return the layout file as the view for this screen..
return thisScreensView;
}// onCreateView...
Then I set the shared preferences string "PASS" with this line of code:
if (loadScreenNickname.length() > 1 && !loadScreenNickname.equalsIgnoreCase("none")) {
// before we launch the next screen...
if (!rememberSwitch.isChecked()) {
voucherTextfieldEditText.setText("");
}
if (rememberSwitch.isChecked()){
//voucherTextfieldEditText.setText("");
//editor.putString(pass, voucherTextfieldEditText.getText().toString().trim());
//editor.apply();
pass = voucherTextfieldEditText.getText().toString();
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString("PASS", pass);
editor.apply();
}
errormessageTextView.setVisibility(View.GONE);
errormessageTextView.invalidate();
//loadScreenObject(null, thisScreen);
try {
loadScreenWithNickname(loadScreenNickname);
foundIt = 1;
} catch (Exception ex){
Log.e ("eTutorPrism Error", "Caught this exception " + ex);
ex.printStackTrace();
}
break;
}
}
However anything I exit and restart the application, even if the remember me button is checked, it doesn't remember the previous value and the app ends up crashing due to a null. Everything else in the code works except the Remember Me. If anyones got any ideas I could use them?
You are not initializing your sharedPreference. try to initialize it.
SharedPreferences sharedPreferences=getSharedPreferences("myPref",MODE_PRIVATE);
Update: I guess the subject gave a wrong notion that I'm looking for an existing addon. This is a custom problem and I do NOT want an existing solution.
I wish to WRITE (or more appropriately, modify and existing) Addon.
Here's my requirement:
I want my addon to work for a particular site only
The data on the pages are encoded using a 2 way hash
A good deal of info is loaded by XHR requests, and sometimes
displayed in animated bubbles etc.
The current version of my addon parses the page via XPath
expressions, decodes the data, and replaces them
The issue comes in with those bubblified boxes that are displayed
on mouse-over event
Thus, I realized that it might be a good idea to create an XHR
bridge that could listen to all the data and decode/encode on the fly
After a couple of searches, I came across nsITraceableInterface[1][2][3]
Just wanted to know if I am on the correct path. If "yes", then kindly
provide any extra pointers and suggestions that may be appropriate;
and if "No", then.. well, please help with correct pointers :)
Thanks,
Bipin.
[1]. https://developer.mozilla.org/en/NsITraceableChannel
[2]. http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
[3]. http://www.ashita.org/howto-xhr-listening-by-a-firefox-addon/
nsITraceableChannel is indeed the way to go here. the blog posts by Jan Odvarko (softwareishard.com) and myself (ashita.org) show how to do this. You may also want to see http://www.ashita.org/implementing-an-xpcom-firefox-interface-and-creating-observers/, however it isn't really necessary to do this in an XPCOM component.
The steps are basically:
Create Object prototype implementing nsITraceableChannel; and create observer to listen to http-on-modify-request and http-on-examine-response
register observer
observer listening to the two request types adds our nsITraceableChannel object into the chain of listeners and make sure that our nsITC knows who is next in the chain
nsITC object provides three callbacks and each will be called at the appropriate stage: onStartRequest, onDataAvailable, and onStopRequest
in each of the callbacks above, our nsITC object must pass on the data to the next item in the chain
Below is actual code from a site-specific add-on I wrote that behaves very similarly to yours from what I can tell.
function TracingListener() {
//this.receivedData = [];
}
TracingListener.prototype =
{
originalListener: null,
receivedData: null, // array for incoming data.
onDataAvailable: function(request, context, inputStream, offset, count)
{
var binaryInputStream = CCIN("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
var storageStream = CCIN("#mozilla.org/storagestream;1", "nsIStorageStream");
binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
var binaryOutputStream = CCIN("#mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream");
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Copy received data as they come.
var data = binaryInputStream.readBytes(count);
//var data = inputStream.readBytes(count);
this.receivedData.push(data);
binaryOutputStream.writeBytes(data, count);
this.originalListener.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count);
},
onStartRequest: function(request, context) {
this.receivedData = [];
this.originalListener.onStartRequest(request, context);
},
onStopRequest: function(request, context, statusCode)
{
try
{
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0)
{
var data = null;
if (request.requestMethod.toLowerCase() == "post")
{
var postText = this.readPostTextFromRequest(request, context);
if (postText)
data = ((String)(postText)).parseQuery();
}
var date = Date.parse(request.getResponseHeader("Date"));
var responseSource = this.receivedData.join('');
//fix leading spaces bug
responseSource = responseSource.replace(/^\s+(\S[\s\S]+)/, "$1");
piratequesting.ProcessRawResponse(request.originalURI.spec, responseSource, date, data);
}
}
catch (e)
{
dumpError(e);
}
this.originalListener.onStopRequest(request, context, statusCode);
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
readPostTextFromRequest : function(request, context) {
try
{
var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream;
if (is)
{
var ss = is.QueryInterface(Ci.nsISeekableStream);
var prevOffset;
if (ss)
{
prevOffset = ss.tell();
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
// Seek locks the file so, seek to the beginning only if necko hasn't read it yet,
// since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed).
if (ss && prevOffset == 0)
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
return text;
}
else {
dump("Failed to Query Interface for upload stream.\n");
}
}
catch(exc)
{
dumpError(exc);
}
return null;
},
readFromStream : function(stream, charset, noClose) {
var sis = CCSV("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
sis.setInputStream(stream);
var segments = [];
for (var count = stream.available(); count; count = stream.available())
segments.push(sis.readBytes(count));
if (!noClose)
sis.close();
var text = segments.join("");
return text;
}
}
hRO = {
observe: function(request, aTopic, aData){
try {
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aTopic == "http-on-examine-response") {
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) {
var newListener = new TracingListener();
request.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = request.setNewListener(newListener);
}
}
} catch (e) {
dump("\nhRO error: \n\tMessage: " + e.message + "\n\tFile: " + e.fileName + " line: " + e.lineNumber + "\n");
}
},
QueryInterface: function(aIID){
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
};
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(hRO,
"http-on-examine-response", false);
In the above code, originalListener is the listener we are inserting ourselves before in the chain. It is vital that you keep that info when creating the Tracing Listener and pass on the data in all three callbacks. Otherwise nothing will work (pages won't even load. Firefox itself is last in the chain).
Note: there are some functions called in the code above which are part of the piratequesting add-on, e.g.: parseQuery() and dumpError()
Tamper Data Add-on. See also the How to Use it page
You could try making a Greasemonkey script and overwriting the XMLHttpRequest.
The code would look something like:
function request () {
};
request.prototype.open = function (type, path, block) {
GM_xmlhttpRequest({
method: type,
url: path,
onload: function (response) {
// some code here
}
});
};
unsafeWindow.XMLHttpRequest = request;
Also note that you can turn a GM script into an addon for Firefox.