Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
android:name=".activities.InterestsActivity"
android:theme="@style/Theme.Capture.NoActionBar" />

<!-- App Widget Provider -->
<receiver
android:name=".widget.LatestPostWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/latest_post_widget_info" />
</receiver>

<!-- FileProvider for camera access -->
<provider
android:name="androidx.core.content.FileProvider"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pineapple.capture.fragment;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
Expand Down Expand Up @@ -51,6 +52,9 @@
import java.util.Map;
import java.util.concurrent.ExecutionException;

import android.appwidget.AppWidgetManager;
import android.content.ComponentName;

public class CameraFragment extends Fragment {

private PreviewView previewView;
Expand Down Expand Up @@ -379,10 +383,15 @@ private void savePostToFirestore(String imageUrl, String content) {
FirebaseFirestore.getInstance().collection("posts")
.add(postData)
.addOnSuccessListener(documentReference -> {
// Success - post created and image uploaded
String postId = documentReference.getId();
Log.d("CameraFragment", "Post saved with ID: " + postId);
Toast.makeText(requireContext(), "Post uploaded successfully!", Toast.LENGTH_SHORT).show();

// Update the home screen widget
updateWidget();

// For testing: verify the post was saved by reading it back
FirebaseFirestore.getInstance().collection("posts").document(postId)
.get()
.addOnSuccessListener(postSnapshot -> {
Expand Down Expand Up @@ -463,4 +472,24 @@ private void resetCameraAfterPost() {
}
}
}

/**
* Update the home screen widget with the latest post
*/
private void updateWidget() {
Intent intent = new Intent(requireContext(), com.pineapple.capture.widget.LatestPostWidget.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);

// Use AppWidgetManager to get the widget IDs
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(requireContext());
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
new ComponentName(requireContext(), com.pineapple.capture.widget.LatestPostWidget.class));

// Add widget IDs to the intent
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

// Send broadcast to update widgets
requireContext().sendBroadcast(intent);
Log.d("CameraFragment", "Widget update broadcast sent");
}
}
177 changes: 177 additions & 0 deletions app/src/main/java/com/pineapple/capture/widget/LatestPostWidget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.pineapple.capture.widget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import android.os.Bundle;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.AppWidgetTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.pineapple.capture.MainActivity;
import com.pineapple.capture.R;
import com.pineapple.capture.feed.FeedItem;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LatestPostWidget extends AppWidgetProvider {

private static final String TAG = "LatestPostWidget";

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}

private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_latest_post);

// Show loading state - hide post views, show empty view with loading text
views.setViewVisibility(R.id.widget_empty_view, View.VISIBLE);
views.setTextViewText(R.id.widget_empty_view, "Loading...");

// Set up click intent for the widget
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_post_image, pendingIntent);

// Update the widget initially with loading state
appWidgetManager.updateAppWidget(appWidgetId, views);

// Fetch the latest post from Firestore
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("posts")
.orderBy("timestamp", Query.Direction.DESCENDING)
.limit(1)
.get()
.addOnSuccessListener(queryDocumentSnapshots -> {
if (!queryDocumentSnapshots.isEmpty()) {
for (QueryDocumentSnapshot document : queryDocumentSnapshots) {
FeedItem latestPost = document.toObject(FeedItem.class);
latestPost.setId(document.getId());

// Update widget with post data
updateWidgetWithPost(context, appWidgetManager, appWidgetId, latestPost);
return;
}
} else {
// No posts found
views.setViewVisibility(R.id.widget_empty_view, View.VISIBLE);
views.setTextViewText(R.id.widget_empty_view, "No posts available");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
})
.addOnFailureListener(e -> {
Log.e(TAG, "Error fetching latest post", e);
views.setViewVisibility(R.id.widget_empty_view, View.VISIBLE);
views.setTextViewText(R.id.widget_empty_view, "Error loading post");
appWidgetManager.updateAppWidget(appWidgetId, views);
});
}

private void updateWidgetWithPost(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, FeedItem post) {
// Create a RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_latest_post);

// Hide empty view
views.setViewVisibility(R.id.widget_empty_view, View.GONE);

// Set caption
if (post.getContent() != null && !post.getContent().isEmpty()) {
views.setTextViewText(R.id.widget_post_caption, post.getContent());
views.setViewVisibility(R.id.widget_post_caption, View.VISIBLE);
} else {
views.setViewVisibility(R.id.widget_post_caption, View.GONE);
}

// Set username
views.setTextViewText(R.id.widget_username, post.getUsername() != null ?
post.getUsername() : "Anonymous");

// Set up click intent to open the app
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("post_id", post.getId());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_post_image, pendingIntent);

// First update with what we have (without images)
appWidgetManager.updateAppWidget(appWidgetId, views);

// Load images in background thread
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
try {
// Load post image with Glide in background thread
if (post.getImageUrl() != null && !post.getImageUrl().isEmpty()) {
try {
Bitmap bitmap = Glide.with(context.getApplicationContext())
.asBitmap()
.load(post.getImageUrl())
.submit(400, 400) // Limit size to prevent OOM
.get();

views.setImageViewBitmap(R.id.widget_post_image, bitmap);
} catch (Exception e) {
Log.e(TAG, "Error loading post image", e);
}
}

// Load user avatar with Glide in background thread
if (post.getProfilePictureUrl() != null && !post.getProfilePictureUrl().isEmpty()) {
try {
Bitmap avatarBitmap = Glide.with(context.getApplicationContext())
.asBitmap()
.load(post.getProfilePictureUrl())
.circleCrop()
.submit(80, 80) // Small size for avatar
.get();

views.setImageViewBitmap(R.id.widget_user_avatar, avatarBitmap);
} catch (Exception e) {
Log.e(TAG, "Error loading avatar image", e);
}
}

// Update widget with images on main thread
handler.post(() -> {
appWidgetManager.updateAppWidget(appWidgetId, views);
});
} catch (Exception e) {
Log.e(TAG, "Error in background loading", e);
}
});
}

@Override
public void onEnabled(Context context) {
// Called when the first widget is created
}

@Override
public void onDisabled(Context context) {
// Called when the last widget is disabled

// Shutdown the executor service if needed
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/widget_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#333333" />
<corners android:radius="12dp" />
</shape>
39 changes: 39 additions & 0 deletions app/src/main/res/drawable/widget_preview.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Background -->
<item>
<shape android:shape="rectangle">
<solid android:color="#333333" />
<corners android:radius="12dp" />
</shape>
</item>

<!-- Post image placeholder -->
<item android:top="8dp" android:left="8dp" android:right="8dp" android:bottom="40dp">
<shape android:shape="rectangle">
<solid android:color="#888888" />
</shape>
</item>

<!-- Caption overlay at the top -->
<item android:top="8dp" android:left="8dp" android:right="8dp" android:height="40dp">
<shape android:shape="rectangle">
<solid android:color="#80000000" />
</shape>
</item>

<!-- Avatar placeholder -->
<item android:left="16dp" android:bottom="16dp" android:width="32dp" android:height="32dp">
<shape android:shape="oval">
<solid android:color="#CCCCCC" />
</shape>
</item>

<!-- Username placeholder -->
<item android:left="56dp" android:bottom="16dp" android:width="80dp" android:height="16dp">
<shape android:shape="rectangle">
<solid android:color="#CCCCCC" />
<corners android:radius="4dp" />
</shape>
</item>
</layer-list>
72 changes: 72 additions & 0 deletions app/src/main/res/layout/widget_latest_post.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:background="@drawable/widget_background">

<!-- Post Image -->
<ImageView
android:id="@+id/widget_post_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="Latest post" />

<!-- Caption overlay at the top -->
<TextView
android:id="@+id/widget_post_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="8dp"
android:background="#80000000"
android:textColor="#FFFFFF"
android:maxLines="2"
android:ellipsize="end"
android:textSize="14sp" />

<!-- Username and avatar container -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_margin="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="#80000000"
android:padding="4dp">

<!-- User Avatar -->
<ImageView
android:id="@+id/widget_user_avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="User avatar" />

<!-- Username -->
<TextView
android:id="@+id/widget_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginStart="4dp" />
</LinearLayout>

<!-- Empty state view -->
<TextView
android:id="@+id/widget_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="14sp"
android:textColor="#FFFFFF"
android:background="#80000000"
android:text="No posts available"
android:visibility="gone" />

</RelativeLayout>
11 changes: 11 additions & 0 deletions app/src/main/res/xml/latest_post_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/widget_latest_post"
android:initialLayout="@layout/widget_latest_post"
android:minWidth="180dp"
android:minHeight="180dp"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen">
</appwidget-provider>