318 lines
10 KiB
JavaScript
318 lines
10 KiB
JavaScript
/**
|
|
* ShareManager.js
|
|
* Minimal share manager - only basic share functionality.
|
|
* Social fission features have been removed in monetization-lite.
|
|
*/
|
|
class ShareManager {
|
|
constructor() {
|
|
// Default share content
|
|
this._shareContent = {
|
|
title: '坦克大战 - 一起来战斗吧!',
|
|
imageUrl: '',
|
|
query: '',
|
|
};
|
|
// Cached temp file path from last canvas capture
|
|
this._cachedImageUrl = '';
|
|
|
|
// Register share menu and callback ONCE at startup.
|
|
// The callback reads this._shareContent dynamically so it always
|
|
// returns the latest share data without needing re-registration.
|
|
try {
|
|
if (typeof wx !== 'undefined') {
|
|
if (wx.showShareMenu) {
|
|
wx.showShareMenu({
|
|
withShareTicket: true,
|
|
menus: ['shareAppMessage', 'shareTimeline'],
|
|
});
|
|
}
|
|
if (wx.onShareAppMessage) {
|
|
wx.onShareAppMessage(() => {
|
|
console.log('[ShareManager] onShareAppMessage callback, query:', this._shareContent.query);
|
|
return {
|
|
title: this._shareContent.title || '坦克大战 - 一起来战斗吧!',
|
|
imageUrl: this._shareContent.imageUrl || this._cachedImageUrl || '',
|
|
query: this._shareContent.query || '',
|
|
};
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ShareManager] constructor share setup failed:', e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a share image from the current game canvas.
|
|
* Outputs a 5:4 portrait-ratio image so WeChat's share card shows
|
|
* the full screen without cropping.
|
|
*
|
|
* Strategy:
|
|
* 1. Try offscreen canvas redraw (ideal — contain-fit into 5:4)
|
|
* 2. Fallback: direct capture with portrait dest dimensions
|
|
*
|
|
* @param {function} [callback] - Called with (tempFilePath) on success.
|
|
*/
|
|
generateShareImage(callback) {
|
|
var self = this;
|
|
try {
|
|
const srcCanvas = GameGlobal && GameGlobal.canvas;
|
|
if (!srcCanvas || typeof wx === 'undefined' || !wx.canvasToTempFilePath) {
|
|
if (callback) callback();
|
|
return;
|
|
}
|
|
|
|
const DPR = GameGlobal.DEVICE_PIXEL_RATIO || 1;
|
|
const SHARE_W = 500;
|
|
const SHARE_H = 625; // 5:4 portrait for WeChat share card
|
|
|
|
const srcW = srcCanvas.width / DPR;
|
|
const srcH = srcCanvas.height / DPR;
|
|
|
|
// --- Try offscreen canvas approach first ---
|
|
var offCanvas = null;
|
|
try {
|
|
offCanvas = wx.createCanvas && wx.createCanvas();
|
|
} catch (e2) {
|
|
offCanvas = null;
|
|
}
|
|
|
|
if (offCanvas) {
|
|
try {
|
|
offCanvas.width = SHARE_W;
|
|
offCanvas.height = SHARE_H;
|
|
const offCtx = offCanvas.getContext('2d');
|
|
if (offCtx) {
|
|
// Dark background
|
|
offCtx.fillStyle = '#0a0e1a';
|
|
offCtx.fillRect(0, 0, SHARE_W, SHARE_H);
|
|
|
|
// Contain-fit source into portrait frame
|
|
const srcRatio = srcW / srcH;
|
|
const dstRatio = SHARE_W / SHARE_H;
|
|
var drawW, drawH, dx, dy;
|
|
if (srcRatio > dstRatio) {
|
|
drawW = SHARE_W;
|
|
drawH = SHARE_W / srcRatio;
|
|
dx = 0;
|
|
dy = (SHARE_H - drawH) / 2;
|
|
} else {
|
|
drawH = SHARE_H;
|
|
drawW = SHARE_H * srcRatio;
|
|
dx = (SHARE_W - drawW) / 2;
|
|
dy = 0;
|
|
}
|
|
offCtx.drawImage(srcCanvas, dx, dy, drawW, drawH);
|
|
|
|
// Export offscreen canvas
|
|
wx.canvasToTempFilePath({
|
|
canvas: offCanvas,
|
|
width: SHARE_W,
|
|
height: SHARE_H,
|
|
destWidth: SHARE_W,
|
|
destHeight: SHARE_H,
|
|
fileType: 'png',
|
|
quality: 0.92,
|
|
success: function(res) {
|
|
if (res.tempFilePath) {
|
|
console.log('[ShareManager] Share image (offscreen):', res.tempFilePath);
|
|
self._cachedImageUrl = res.tempFilePath;
|
|
if (callback) callback(res.tempFilePath);
|
|
} else {
|
|
self._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
|
|
}
|
|
},
|
|
fail: function() {
|
|
self._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
|
|
},
|
|
});
|
|
return; // done via offscreen path
|
|
}
|
|
} catch (e3) {
|
|
console.warn('[ShareManager] Offscreen canvas draw failed:', e3);
|
|
}
|
|
}
|
|
|
|
// --- Fallback: direct canvas capture with portrait output ---
|
|
this._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
|
|
|
|
} catch (e) {
|
|
console.warn('[ShareManager] generateShareImage error:', e);
|
|
if (callback) callback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fallback: export main canvas directly with portrait dest dimensions.
|
|
*/
|
|
_fallbackDirectCapture(canvas, srcW, srcH, outW, outH, callback) {
|
|
try {
|
|
wx.canvasToTempFilePath({
|
|
canvas: canvas,
|
|
width: srcW,
|
|
height: srcH,
|
|
destWidth: outW,
|
|
destHeight: outH,
|
|
fileType: 'png',
|
|
quality: 0.92,
|
|
success: function(res) {
|
|
if (res.tempFilePath) {
|
|
console.log('[ShareManager] Share image (direct):', res.tempFilePath);
|
|
this._cachedImageUrl = res.tempFilePath;
|
|
}
|
|
if (callback) callback(res && res.tempFilePath ? res.tempFilePath : undefined);
|
|
}.bind(this),
|
|
fail: function(err) {
|
|
console.warn('[ShareManager] Direct canvasToTempFilePath failed:', err);
|
|
if (callback) callback();
|
|
},
|
|
});
|
|
} catch (e) {
|
|
console.warn('[ShareManager] _fallbackDirectCapture error:', e);
|
|
if (callback) callback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update open data for friend ranking.
|
|
* @param {number} score
|
|
* @param {number} level
|
|
*/
|
|
updateOpenData(score, level) {
|
|
try {
|
|
if (typeof wx !== 'undefined' && wx.setUserCloudStorage) {
|
|
wx.setUserCloudStorage({
|
|
KVDataList: [
|
|
{ key: 'score', value: String(score) },
|
|
{ key: 'level', value: String(level) },
|
|
],
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ShareManager] updateOpenData failed:', e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-register the onShareAppMessage callback so the latest
|
|
* this._shareContent is captured. Called internally whenever
|
|
* share content changes.
|
|
*/
|
|
_refreshShareCallback() {
|
|
try {
|
|
if (typeof wx !== 'undefined' && wx.onShareAppMessage) {
|
|
wx.onShareAppMessage(() => {
|
|
console.log('[ShareManager] onShareAppMessage callback fired, query:', this._shareContent.query);
|
|
return {
|
|
title: this._shareContent.title || '坦克大战 - 一起来战斗吧!',
|
|
imageUrl: this._shareContent.imageUrl || '',
|
|
query: this._shareContent.query || '',
|
|
};
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ShareManager] _refreshShareCallback failed:', e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set share content for the passive share callback (right-corner ··· menu).
|
|
* Also re-registers the onShareAppMessage callback to guarantee the
|
|
* latest content is used when the user taps the share button.
|
|
* @param {object} opts - { title, imageUrl, query }
|
|
*/
|
|
setShareContent(opts) {
|
|
this._shareContent = opts || {};
|
|
console.log('[ShareManager] Share content updated:', JSON.stringify(this._shareContent));
|
|
// Auto-generate canvas screenshot as share image if none provided
|
|
if (!this._shareContent.imageUrl && !this._cachedImageUrl) {
|
|
this.generateShareImage();
|
|
}
|
|
// Re-register callback to ensure WeChat picks up the new content
|
|
this._refreshShareCallback();
|
|
}
|
|
|
|
/**
|
|
* Trigger a share action (e.g. team invite).
|
|
* MUST be called within a user-initiated touch event call stack so that
|
|
* wx.shareAppMessage() is allowed by WeChat policy.
|
|
* Also updates the passive share callback as a fallback for the ··· menu.
|
|
* @param {object} opts - { title, imageUrl, query }
|
|
*/
|
|
triggerShare(opts) {
|
|
const data = opts || {};
|
|
// Update passive share callback (right-corner ··· menu fallback)
|
|
this.setShareContent(data);
|
|
|
|
// Ensure we have a share image — generate synchronously-style via cache
|
|
const imageUrl = data.imageUrl || this._cachedImageUrl || '';
|
|
|
|
// Directly invoke wx.shareAppMessage() to open the friend-picker panel.
|
|
// This is permitted because triggerShare is called from a touchstart handler.
|
|
try {
|
|
if (typeof wx !== 'undefined' && wx.shareAppMessage) {
|
|
console.log('[ShareManager] Calling wx.shareAppMessage with query:', data.query);
|
|
wx.shareAppMessage({
|
|
title: data.title || '',
|
|
imageUrl: imageUrl,
|
|
query: data.query || '',
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ShareManager] wx.shareAppMessage failed, falling back to toast:', e);
|
|
// Fallback: prompt user to use the ··· menu
|
|
try {
|
|
if (typeof wx !== 'undefined' && wx.showToast) {
|
|
wx.showToast({
|
|
title: '请点击右上角「···」转发给好友',
|
|
icon: 'none',
|
|
duration: 2500,
|
|
});
|
|
}
|
|
} catch (e2) {
|
|
console.warn('[ShareManager] triggerShare fallback failed:', e2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset share content to default (clear team invite data).
|
|
* Called when leaving the team room.
|
|
*/
|
|
resetShareContent() {
|
|
this._shareContent = {
|
|
title: '坦克大战 - 一起来战斗吧!',
|
|
imageUrl: '',
|
|
query: '',
|
|
};
|
|
console.log('[ShareManager] Share content reset to default');
|
|
this._refreshShareCallback();
|
|
}
|
|
|
|
/**
|
|
* Share a challenge to friends.
|
|
* Sets the share content and prompts the user to share via the menu.
|
|
* @param {number} level
|
|
* @param {number} score
|
|
*/
|
|
shareChallenge(level, score) {
|
|
this.setShareContent({
|
|
title: `我在坦克大战第${level}关拿了${score}分!你能超过我吗?`,
|
|
imageUrl: '',
|
|
query: '',
|
|
});
|
|
try {
|
|
if (typeof wx !== 'undefined' && wx.showToast) {
|
|
wx.showToast({
|
|
title: '请点击右上角「···」分享给好友',
|
|
icon: 'none',
|
|
duration: 2500,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ShareManager] shareChallenge failed:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = ShareManager;
|