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
20 changes: 20 additions & 0 deletions android/app/src/main/java/com/cloudchewie/loftify/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.cloudchewie.loftify;

import android.os.Build
import android.content.ClipboardManager
import android.content.Context
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
Expand All @@ -21,6 +23,24 @@ class MainActivity : FlutterFragmentActivity() {

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
registerWith(flutterEngine);
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"loftify/clipboard"
).setMethodCallHandler { call, result ->
if (call.method != "getMetadata") {
result.notImplemented()
return@setMethodCallHandler
}
try {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val timestamp = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
clipboard.primaryClipDescription?.timestamp ?: 0L
} else 0L
result.success(mapOf("revision" to timestamp.takeIf { it > 0 }?.toString()))
} catch (_: SecurityException) {
result.error("clipboard_unavailable", "Clipboard access unavailable", null)
}
}
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
backDesktopChannel
Expand Down
3 changes: 3 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
A71000000000000000000001 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A71000000000000000000002 /* PrivacyInfo.xcprivacy */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
Expand Down Expand Up @@ -40,6 +41,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
A71000000000000000000002 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Runner/PrivacyInfo.xcprivacy; sourceTree = SOURCE_ROOT; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -213,6 +215,7 @@
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
A71000000000000000000001 /* PrivacyInfo.xcprivacy in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
Expand Down
15 changes: 15 additions & 0 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ import UIKit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private var clipboardChannel: FlutterMethodChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
if let controller = window?.rootViewController as? FlutterViewController {
clipboardChannel = FlutterMethodChannel(
name: "loftify/clipboard", binaryMessenger: controller.binaryMessenger)
clipboardChannel?.setMethodCallHandler { call, result in
guard call.method == "getMetadata" else {
result(FlutterMethodNotImplemented)
return
}
result([
"revision": String(UIPasteboard.general.changeCount),
"uptimeMs": Int64(ProcessInfo.processInfo.systemUptime * 1000)
])
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
15 changes: 15 additions & 0 deletions ios/Runner/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>35F9.1</string></array>
</dict>
</array>
</dict>
</plist>
54 changes: 50 additions & 4 deletions lib/Models/post_detail_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ class Comment {
int blogId;
String content;
List<CommentEmote> emotes;
List<CommentImage> images;
int id;
String ipLocation;
List<Comment> l2Comments;
Expand All @@ -927,6 +928,7 @@ class Comment {
required this.blogId,
required this.content,
required this.emotes,
this.images = const [],
required this.id,
required this.ipLocation,
required this.l2Comments,
Expand All @@ -950,6 +952,14 @@ class Comment {
l2CommentOffset: -1,
blogId: _detailInt(json['blogId']),
content: _detailString(json['content']),
images: json['images'] is List
? (json['images'] as List)
.whereType<Map>()
.map((value) => CommentImage.fromJson(
Map<String, dynamic>.from(value),
))
.toList()
: [],
emotes: json['emotes'] is List
? (json['emotes'] as List)
.whereType<Map>()
Expand Down Expand Up @@ -992,6 +1002,7 @@ class Comment {
data['blogId'] = blogId;
data['content'] = content;
data['emotes'] = emotes.map((v) => v.toJson()).toList();
data['images'] = images.map((v) => v.toJson()).toList();
data['id'] = id;
data['ipLocation'] = ipLocation;
data['l2Comments'] = l2Comments;
Expand All @@ -1008,6 +1019,41 @@ class Comment {
}
}

/// Uploaded comment pictures/stickers are separate from named emotes.
class CommentImage {
const CommentImage({
required this.orign,
this.raw = '',
this.ow = 0,
this.oh = 0,
this.type = 0,
});

final String orign;
final String raw;
final int ow;
final int oh;
final int type;

String get url => orign.trim().isNotEmpty ? orign.trim() : raw.trim();

factory CommentImage.fromJson(Map<String, dynamic> json) => CommentImage(
orign: _detailString(json['orign']),
raw: _detailString(json['raw']),
ow: _detailInt(json['ow']),
oh: _detailInt(json['oh']),
type: _detailInt(json['type']),
);

Map<String, dynamic> toJson() => {
'orign': orign,
'raw': raw,
'ow': ow,
'oh': oh,
'type': type,
};
}

class CommentEmote {
int id;
String name;
Expand All @@ -1023,10 +1069,10 @@ class CommentEmote {

factory CommentEmote.fromJson(Map<String, dynamic> json) {
return CommentEmote(
id: json['id'],
name: json['name'],
sizeType: json['sizeType'],
url: json['url'],
id: _detailInt(json['id']),
name: _detailString(json['name']),
sizeType: _detailInt(json['sizeType']),
url: _detailString(json['url']),
);
}

Expand Down
1 change: 1 addition & 0 deletions lib/Screens/Info/favorite_folder_list_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class _FavoriteFolderListScreenState
? Column(
children: [
ShadowIconButton(
boxShadow: const [],
icon: const ChewieIcon(LoftifyIcons.add),
onTap: handleAdd,
),
Expand Down
2 changes: 2 additions & 0 deletions lib/Screens/Info/history_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ class _HistoryScreenState extends BaseDynamicState<HistoryScreen>
? Column(
children: [
ShadowIconButton(
boxShadow: const [],
backgroundColor: ChewieTheme.canvasColor,
icon: const ChewieIcon(LoftifyIcons.moreVertical),
onTap: () {
BottomSheetBuilder.showContextMenu(
Expand Down
1 change: 1 addition & 0 deletions lib/Screens/Info/like_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ class _LikeScreenState extends BaseDynamicState<LikeScreen>
? Column(
children: [
ShadowIconButton(
boxShadow: const [],
icon: const ChewieIcon(LoftifyIcons.moreVertical),
onTap: () {
BottomSheetBuilder.showContextMenu(
Expand Down
1 change: 1 addition & 0 deletions lib/Screens/Info/share_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ class _ShareScreenState extends BaseDynamicState<ShareScreen>
? Column(
children: [
ShadowIconButton(
boxShadow: const [],
icon: const ChewieIcon(LoftifyIcons.moreVertical),
onTap: () {
BottomSheetBuilder.showContextMenu(
Expand Down
77 changes: 43 additions & 34 deletions lib/Screens/Info/user_detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ class UserDetailScreenState extends BaseDynamicState<UserDetailScreen>
? ExtendedNestedScrollView(
onlyOneScrollInBody: true,
headerSliverBuilder: (_, __) => _buildHeaderSlivers(),
body: _mainContent(),
body: ResponsiveUtil.isLandscapeLayout()
? LoftifyProfileContentFrame(child: _mainContent())
: _mainContent(),
)
: LoadingWidget(
background: ChewieTheme.getBackground(context),
Expand Down Expand Up @@ -269,18 +271,11 @@ class UserDetailScreenState extends BaseDynamicState<UserDetailScreen>
} else {
return [
SliverToBoxAdapter(
child: SizedBox(
height: _profileHeaderHeight(
context,
includesAppBar: false,
),
child: Stack(
fit: StackFit.expand,
children: [
_buildHeaderBackground(),
_buildInfo(12),
],
),
child: Stack(
children: [
Positioned.fill(child: _buildHeaderBackground()),
_buildInfo(16),
],
),
),
SliverPersistentHeader(
Expand All @@ -289,13 +284,17 @@ class UserDetailScreenState extends BaseDynamicState<UserDetailScreen>
delegate: SliverAppBarDelegate(
radius: 0,
background: context.design.colors.page,
tabBar: TabBarWrapper(
tabController: _tabController,
tabs: tabList,
width: MediaQuery.sizeOf(context).width,
isScrollable: false,
showBorder: true,
onTap: _setCurrentTab,
tabBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: LoftifyProfileContentFrame(
child: TabBarWrapper(
tabController: _tabController,
tabs: tabList,
isScrollable: false,
showBorder: true,
onTap: _setCurrentTab,
),
),
),
),
),
Expand Down Expand Up @@ -593,20 +592,30 @@ class UserDetailScreenState extends BaseDynamicState<UserDetailScreen>
design.spacing.lg,
),
child: LoftifyProfileHeaderLayout(
summary: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
_buildIdentitySummary(
hasRemarkName: hasRemarkName,
hasDescription: hasDescription,
),
SizedBox(height: design.spacing.lg),
_buildStatisticsCard(),
SizedBox(height: design.spacing.sectionTop),
_buildProfileAction(),
],
),
stackShowcase: ResponsiveUtil.isLandscapeLayout(),
summary: ResponsiveUtil.isLandscapeLayout()
? LoftifyWideProfileSummary(
identity: _buildIdentitySummary(
hasRemarkName: hasRemarkName,
hasDescription: hasDescription,
),
statistics: _buildStatisticsCard(),
action: _buildProfileAction(),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
_buildIdentitySummary(
hasRemarkName: hasRemarkName,
hasDescription: hasDescription,
),
SizedBox(height: design.spacing.lg),
_buildStatisticsCard(),
SizedBox(height: design.spacing.sectionTop),
_buildProfileAction(),
],
),
showcase: showCases.isNotEmpty ? _buildShowCases() : null,
),
);
Expand Down
4 changes: 4 additions & 0 deletions lib/Screens/Navigation/dynamic_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ class DynamicScreenState extends BaseDynamicState<DynamicScreen>
? Column(
children: [
ShadowIconButton(
boxShadow: const [],
backgroundColor: ChewieTheme.canvasColor,
icon: RotationTransition(
turns: Tween(begin: 0.0, end: 1.0)
.animate(_refreshRotationController),
Expand All @@ -263,6 +265,8 @@ class DynamicScreenState extends BaseDynamicState<DynamicScreen>
),
const SizedBox(height: 10),
ShadowIconButton(
boxShadow: const [],
backgroundColor: ChewieTheme.canvasColor,
icon: const ChewieIcon(LoftifyIcons.scrollTop),
onTap: () {
scrollToTop();
Expand Down
Loading
Loading